Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad950e4c32 | ||
|
|
0e9710d4dd | ||
|
|
9317bf6465 | ||
|
|
1f7c148be2 | ||
|
|
91863dd854 | ||
|
|
2f27ebb414 | ||
|
|
89391e42c8 | ||
|
|
8f30d2a604 | ||
|
|
24c0bf5c5b | ||
|
|
b313fa4166 | ||
|
|
f613873de0 | ||
|
|
405d14fd44 | ||
|
|
2d4b9401f1 | ||
|
|
521e05cb8b | ||
|
|
7afd1724dd | ||
|
|
eee173a64e | ||
|
|
126348536a | ||
|
|
e030e351fd | ||
|
|
720ea82eab | ||
|
|
0a8a45f94d | ||
|
|
f44b6a0e9a | ||
|
|
1797c5e374 | ||
|
|
683a4ce4b2 | ||
|
|
2322f3d87b | ||
|
|
f084778cea | ||
|
|
658f9bf8c4 | ||
|
|
9c76b1ab55 | ||
|
|
e12aacf3a2 | ||
|
|
137696d808 | ||
|
|
37b3b2a7e0 | ||
|
|
5453899878 | ||
|
|
3148f3e8a5 | ||
|
|
3ebace932d | ||
|
|
423c78f4fa | ||
|
|
de4d78d7df | ||
|
|
e5e642aa9b | ||
|
|
52cccfbe94 | ||
|
|
052e01e75d | ||
|
|
5d66aa3d22 | ||
|
|
2f94d40e02 | ||
|
|
d345d1da9b | ||
|
|
0a3ae0f0a4 | ||
|
|
6029bd560e | ||
|
|
39e5a6fa2f | ||
|
|
4ac3d79bfc | ||
|
|
51fa40ff42 | ||
|
|
042512aa36 | ||
|
|
3f39c18ac1 | ||
|
|
d8102f5ff8 | ||
|
|
65ad4ff87e | ||
|
|
ec5564a4cf | ||
|
|
2cd5f6f3ff | ||
|
|
fbcd591b28 | ||
|
|
e1b17f1232 | ||
|
|
8c76e0f288 | ||
|
|
b4de9e832d | ||
|
|
70aaf50082 | ||
|
|
66472a5ee3 | ||
|
|
fd7bde5726 | ||
|
|
9d40f1cbb5 | ||
|
|
389c5e14ad | ||
|
|
64c657a8af | ||
|
|
e6f67092f8 | ||
|
|
3053337043 | ||
|
|
215c5eacb0 | ||
|
|
afb48c6cc9 | ||
|
|
7fddebc272 | ||
|
|
28d20b8f14 | ||
|
|
ba2feb3004 | ||
|
|
aa0606db08 | ||
|
|
49c1c96fc1 | ||
|
|
d92f2f5ad6 | ||
|
|
e1c09d5982 | ||
|
|
8d9e75c769 | ||
|
|
2643c691be | ||
|
|
ef78757e69 | ||
|
|
35e697de83 | ||
|
|
159981e73c | ||
|
|
ec06e93319 | ||
|
|
d6a8e64e69 | ||
|
|
a06f10b009 | ||
|
|
0a26869a13 | ||
|
|
2c529844fc | ||
|
|
33f7e12b88 | ||
|
|
90891a7dde | ||
|
|
4a59230d82 | ||
|
|
e24b947c9b | ||
|
|
c5a753c890 | ||
|
|
64327e4169 | ||
|
|
aab9b12786 | ||
|
|
066b68328e | ||
|
|
db3e3ebcb2 | ||
|
|
86db5091c7 | ||
|
|
93c1d31d97 | ||
|
|
8fca090ede | ||
|
|
e38d0b5a5f | ||
|
|
94013b8ecb | ||
|
|
968cd2b967 | ||
|
|
19c2b179c7 | ||
|
|
3ba7c6acbe | ||
|
|
936ee01759 | ||
|
|
8a0515d9a3 | ||
|
|
21ab5d4f78 | ||
|
|
0fb96d55bd | ||
|
|
f5d8a36983 | ||
|
|
11b602fbf8 | ||
|
|
b2ce3719af | ||
|
|
21414ad480 | ||
|
|
13c1e1f298 | ||
|
|
34505d26f7 | ||
|
|
4930d19e70 | ||
|
|
aba9517542 | ||
|
|
7540708a61 | ||
|
|
664c7b36a5 | ||
|
|
c390972c76 | ||
|
|
73d286d523 | ||
|
|
4cbccab2bd | ||
|
|
23ed37dd87 | ||
|
|
9b25484a13 | ||
|
|
49f8f43890 | ||
|
|
c936f801c0 | ||
|
|
694a869e78 | ||
|
|
a2e8a3a241 | ||
|
|
2f8ec0e024 | ||
|
|
09c63c45dd | ||
|
|
1ed0f049fe | ||
|
|
4b04ca0b18 | ||
|
|
2f54107289 | ||
|
|
e34f06ae5d | ||
|
|
77386ae011 | ||
|
|
0c959294a8 | ||
|
|
8892b5cf9e | ||
|
|
9da232c05a | ||
|
|
1ce06604de | ||
|
|
530c70b0a9 | ||
|
|
dadc67a92e | ||
|
|
2775a3526e | ||
|
|
8f241ab3b8 | ||
|
|
3e09be56a8 | ||
|
|
757f8f771e | ||
|
|
04ba485042 | ||
|
|
b1149a49b2 | ||
|
|
db09f6eb2e | ||
|
|
835d15b536 | ||
|
|
bae0512c8a | ||
|
|
8d6134952a | ||
|
|
1d7c0e5949 | ||
|
|
37b0f3617d | ||
|
|
6866117194 | ||
|
|
b622537199 | ||
|
|
355b1b6a57 | ||
|
|
d6f4273241 | ||
|
|
de1b19ea88 | ||
|
|
79fa5618f1 | ||
|
|
91bf2ab86d | ||
|
|
88655019cc | ||
|
|
fad44611dc | ||
|
|
8884971223 | ||
|
|
07e1248686 | ||
|
|
6cd84b64f0 | ||
|
|
c6b3966d02 | ||
|
|
432e056659 | ||
|
|
bbb6c2fb53 | ||
|
|
095a6e5937 | ||
|
|
56020fd821 | ||
|
|
11235c0e40 | ||
|
|
406caa5f56 | ||
|
|
8bd5e39102 | ||
|
|
c547dff650 | ||
|
|
e6df2f6210 | ||
|
|
312ccae003 | ||
|
|
ff27217639 | ||
|
|
3f4f9b6331 | ||
|
|
5b8e395174 | ||
|
|
9914b676b3 | ||
|
|
abd44c8393 | ||
|
|
f70916f8b0 | ||
|
|
ef3ad68020 | ||
|
|
f9470c9a17 | ||
|
|
e9a1438ac4 | ||
|
|
5102c0da92 | ||
|
|
d68b775df5 | ||
|
|
ed995ffa03 | ||
|
|
875a1f07d8 | ||
|
|
d070546568 | ||
|
|
511be70eba | ||
|
|
840f7f61c5 | ||
|
|
9d69d6e8fb | ||
|
|
15c078df61 | ||
|
|
e90e5640e7 | ||
|
|
2df6695f2b | ||
|
|
92e3b1802f | ||
|
|
0463520693 | ||
|
|
922e3bf59f | ||
|
|
42f2ad0516 | ||
|
|
1eab6202ac | ||
|
|
d30ee6c44a | ||
|
|
4f2da00e94 | ||
|
|
34b9a3fa2e | ||
|
|
5c7b869e61 | ||
|
|
6cbea7d29b | ||
|
|
8cfa37fc21 | ||
|
|
16c53085f7 | ||
|
|
2ccb926314 | ||
|
|
6384fe3414 | ||
|
|
ef64333e63 | ||
|
|
0851705304 | ||
|
|
b97a4ad7f2 | ||
|
|
d01afe885c | ||
|
|
959d6035e5 | ||
|
|
aa730ef1ea | ||
|
|
f5ca1f9ee9 | ||
|
|
61d3fdb474 | ||
|
|
53a4d6c3b1 | ||
|
|
f40ca9cb58 | ||
|
|
7a3d1487e4 | ||
|
|
3a955d9ad4 | ||
|
|
0413fbc7d0 | ||
|
|
c0f5c678c2 | ||
|
|
a9897b0084 | ||
|
|
f4b5a4d869 | ||
|
|
a60016e061 | ||
|
|
374401d786 | ||
|
|
7d749d810b | ||
|
|
d63d02601f | ||
|
|
0a2621c673 | ||
|
|
07601b7e07 | ||
|
|
645c664fbb | ||
|
|
d7920ff9b1 | ||
|
|
c6cd1e59b1 | ||
|
|
4938052f6b | ||
|
|
b278e44376 | ||
|
|
c07e16fadd | ||
|
|
38da8d07d0 | ||
|
|
733daef127 | ||
|
|
175a9e3199 | ||
|
|
5e3a15ae1b | ||
|
|
a0e19e3cf1 | ||
|
|
cd03e293c3 | ||
|
|
d96f47cf06 | ||
|
|
4c14e8668c | ||
|
|
4de1031d8a | ||
|
|
073598ed8f | ||
|
|
808f3004f0 | ||
|
|
4724dd439e | ||
|
|
3b74b55084 | ||
|
|
a3da3e592d | ||
|
|
1cab962098 | ||
|
|
1eb85249e7 | ||
|
|
44d8b039e8 | ||
|
|
370ab4c4db | ||
|
|
843f769b96 | ||
|
|
d5935bb0a4 | ||
|
|
e5d50b236c | ||
|
|
07f39ae47b | ||
|
|
569a3f4548 | ||
|
|
636aa8de76 | ||
|
|
68ba4d43ac | ||
|
|
2c120af61e | ||
|
|
91f4f41665 | ||
|
|
6a052af890 | ||
|
|
5629dbe715 | ||
|
|
8cbd858d45 | ||
|
|
28375ff63e | ||
|
|
cd81dcf18b | ||
|
|
65311a3360 | ||
|
|
315df9ada0 | ||
|
|
036963a7da | ||
|
|
54fd3d4024 | ||
|
|
a79bb86856 | ||
|
|
e138698e54 | ||
|
|
db6655deb4 | ||
|
|
cf0a909e2d | ||
|
|
1976acfe3e | ||
|
|
497351ad07 | ||
|
|
4a2c4477c9 | ||
|
|
98d2e0017d | ||
|
|
7de6451818 | ||
|
|
3060de88ce | ||
|
|
f6e1a94e03 | ||
|
|
dc39b0c190 | ||
|
|
ccc39f64f8 | ||
|
|
b45b0d81bb | ||
|
|
d026647f4f | ||
|
|
a2af2a306b | ||
|
|
725762c48d | ||
|
|
6bf81eb7b1 | ||
|
|
eef45934fa | ||
|
|
c4ef1efdb7 | ||
|
|
c3df27d1ea | ||
|
|
2f717c156a | ||
|
|
a7c02b19bf | ||
|
|
0418253a9b | ||
|
|
9ae4f5c73d | ||
|
|
169be0426c | ||
|
|
6097d14d4d | ||
|
|
f657315e60 | ||
|
|
a19c4058be | ||
|
|
ec34806c1e | ||
|
|
f141329fb7 | ||
|
|
6da2bc96e2 | ||
|
|
95360c2e0c | ||
|
|
18b59f35ef | ||
|
|
40d555dbb7 | ||
|
|
df5f543b65 | ||
|
|
e1d98bca5a | ||
|
|
dba33a943d | ||
|
|
84d056d0f7 | ||
|
|
3a5905fa85 | ||
|
|
5b48d90049 | ||
|
|
3c09ce26fb | ||
|
|
a4f03bd7eb | ||
|
|
4ae1bea323 | ||
|
|
b70d81f60b | ||
|
|
c945e1126a | ||
|
|
3d0b427c30 | ||
|
|
ceb3a7a8f6 | ||
|
|
61a73de64d | ||
|
|
7778f1b4b0 | ||
|
|
62a197516b | ||
|
|
95d6297db8 | ||
|
|
53774e69fa | ||
|
|
1ccfd6a1f7 | ||
|
|
25f8d4fb41 | ||
|
|
af2175326c | ||
|
|
9b4edfdf08 | ||
|
|
817894c012 | ||
|
|
7d81f39222 | ||
|
|
e58118cda6 | ||
|
|
20377a6f3d | ||
|
|
f491eee2a1 | ||
|
|
e8e6ca6309 | ||
|
|
367ba4125f | ||
|
|
3d9c6a7ce6 | ||
|
|
55c74617d7 | ||
|
|
3c7d6a1853 | ||
|
|
f07b7393a0 | ||
|
|
ac91822952 | ||
|
|
ea1b48bd12 | ||
|
|
fba391ae66 | ||
|
|
257009afff | ||
|
|
5d180d2834 | ||
|
|
0bad00d049 | ||
|
|
92bde124ca | ||
|
|
7018ad69a2 | ||
|
|
46df4db87b | ||
|
|
c19530bc71 | ||
|
|
349ef3f6a0 | ||
|
|
a031d268b1 | ||
|
|
81a55d026f | ||
|
|
1bf02e1ca9 | ||
|
|
a66814fd7a | ||
|
|
18ff8cd1fb | ||
|
|
17c226b070 | ||
|
|
6122f1c450 | ||
|
|
4a474843fb | ||
|
|
7d43159a7d | ||
|
|
3ea4c43add | ||
|
|
c6f0ea2c81 | ||
|
|
60d5ef4d82 | ||
|
|
21316d5709 | ||
|
|
aba61d9ebd | ||
|
|
965356ae63 | ||
|
|
d1b5767a82 | ||
|
|
ae7d5bf617 | ||
|
|
e3667cb908 | ||
|
|
f745c0181a | ||
|
|
dd885780d6 | ||
|
|
95ea74549c |
@@ -1,12 +1,17 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Submit a crash report
|
||||
title: '[CRASH]'
|
||||
title: '[CRASH] <short description>'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Notice!
|
||||
- If a Redis module was involved, please open an issue in the module's repo instead!
|
||||
- If you're using docker on Apple M1, please make sure the image you're using was compiled for ARM!
|
||||
|
||||
|
||||
**Crash report**
|
||||
|
||||
Paste the complete crash log between the quotes below. Please include a few lines from the log preceding the crash report to provide some context.
|
||||
|
||||
@@ -19,12 +19,15 @@ jobs:
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
|
||||
build-ubuntu-old:
|
||||
runs-on: ubuntu-16.04
|
||||
build-debian-old:
|
||||
runs-on: ubuntu-latest
|
||||
container: debian:oldoldstable
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror'
|
||||
run: |
|
||||
apt-get update && apt-get install -y build-essential
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
|
||||
build-macos-latest:
|
||||
runs-on: macos-latest
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6
|
||||
@@ -28,6 +28,8 @@ jobs:
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
- name: unittest
|
||||
run: ./src/redis-server test all
|
||||
|
||||
test-ubuntu-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -48,6 +50,25 @@ jobs:
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-ubuntu-no-malloc-usable-size:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make MALLOC=libc CFLAGS=-DNO_MALLOC_USABLE_SIZE
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6
|
||||
./runtest --accurate --verbose --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-ubuntu-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
@@ -57,7 +78,7 @@ jobs:
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386
|
||||
make 32bit
|
||||
make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6
|
||||
@@ -70,6 +91,8 @@ jobs:
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
- name: unittest
|
||||
run: ./src/redis-server test all
|
||||
|
||||
test-ubuntu-tls:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -123,12 +146,32 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make valgrind
|
||||
run: make valgrind REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 valgrind -y
|
||||
./runtest --valgrind --verbose --clients 1 --dump-logs
|
||||
./runtest --valgrind --verbose --clients 1 --tags -large-memory --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
|
||||
- name: unittest
|
||||
run: |
|
||||
valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/redis-server test all
|
||||
if grep -q 0x err.txt; then cat err.txt; exit 1; fi
|
||||
|
||||
test-valgrind-no-malloc-usable-size:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make valgrind CFLAGS="-DNO_MALLOC_USABLE_SIZE"
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 valgrind -y
|
||||
./runtest --valgrind --verbose --clients 1 --tags -large-memory --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1
|
||||
|
||||
@@ -204,20 +247,63 @@ jobs:
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-freebsd:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-12
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: test
|
||||
uses: vmactions/freebsd-vm@v0.1.2
|
||||
uses: vmactions/freebsd-vm@v0.3.0
|
||||
with:
|
||||
usesh: true
|
||||
sync: rsync
|
||||
copyback: false
|
||||
prepare: pkg install -y bash gmake lang/tcl86
|
||||
run: >
|
||||
gmake &&
|
||||
./runtest --accurate --verbose --no-latency --dump-logs &&
|
||||
./runtest --accurate --verbose --no-latency --tags -large-memory --dump-logs &&
|
||||
MAKE=gmake ./runtest-moduleapi --verbose &&
|
||||
./runtest-sentinel &&
|
||||
./runtest-cluster
|
||||
|
||||
test-alpine-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: alpine:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
apk add build-base
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
- name: test
|
||||
run: |
|
||||
apk add tcl procps
|
||||
./runtest --accurate --verbose --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-alpine-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: alpine:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
apk add build-base
|
||||
make REDIS_CFLAGS='-Werror' USE_JEMALLOC=no CFLAGS=-DUSE_MALLOC_USABLE_SIZE
|
||||
- name: test
|
||||
run: |
|
||||
apk add tcl procps
|
||||
./runtest --accurate --verbose --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
+479
@@ -11,6 +11,485 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
Redis Community Edition 6.2.16 Released Wed 02 Oct 2024 20:17:04 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security fixes
|
||||
==============
|
||||
|
||||
* (CVE-2024-31449) Lua library commands may lead to stack overflow and potential RCE.
|
||||
* (CVE-2024-31228) Potential Denial-of-service due to unbounded pattern matching.
|
||||
|
||||
6.2.15 was an erroneous release,
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.14 Released Wed 18 Oct 2023 10:33:40 IDT
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security fixes
|
||||
==============
|
||||
|
||||
* (CVE-2023-45145) The wrong order of listen(2) and chmod(2) calls creates a
|
||||
race condition that can be used by another process to bypass desired Unix
|
||||
socket permissions on startup.
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.13 Released Mon July 10 12:00:00 IDT 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency SECURITY: See security fixes below.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2022-24834) A specially crafted Lua script executing in Redis can trigger
|
||||
a heap overflow in the cjson and cmsgpack libraries, and result in heap
|
||||
corruption and potentially remote code execution. The problem exists in all
|
||||
versions of Redis with Lua scripting support, starting from 2.6, and affects
|
||||
only authenticated and authorized users.
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Re-enable downscale rehashing while there is a fork child (#12276)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.12 Released Mon Apr 17 16:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2023-28856) Authenticated users can use the HINCRBYFLOAT command to create
|
||||
an invalid hash field that will crash Redis on access
|
||||
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Fix CLIENT REPLY OFF|SKIP to not silence push notifications (#11875)
|
||||
* Disconnect pub-sub subscribers when revoking allchannels permission (#11992)
|
||||
* Trim excessive memory usage in stream nodes when exceeding `stream-node-max-bytes` (#11885)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.11 Released Tue Feb 28 12:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2023-25155) Specially crafted SRANDMEMBER, ZRANDMEMBER, and HRANDFIELD
|
||||
commands can trigger an integer overflow, resulting in a runtime assertion
|
||||
and termination of the Redis server process.
|
||||
* (CVE-2022-36021) String matching commands (like SCAN or KEYS) with a specially
|
||||
crafted pattern to trigger a denial-of-service attack on Redis, causing it to
|
||||
hang and consume 100% CPU time.
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Fix a crash when reaching the maximum invalidations limit of client-side tracking (#11814)
|
||||
* Fix cluster inbound link keepalive time (#11785)
|
||||
* Make sure that fork child doesn't do incremental rehashing (#11692)
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Avoid realloc to reduce size of strings when it is unneeded (#11766)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.10 Released Mon Jan 17 12:00:00 IST 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: MODERATE, a quick followup fix for a recently released 6.2.9.
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Revert the change to KEYS in the recent client output buffer limit fix (#11676)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.9 Released Mon Jan 16 12:00:00 IDT 2023
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2022-35977) Integer overflow in the Redis SETRANGE and SORT/SORT_RO
|
||||
commands can drive Redis to OOM panic
|
||||
* (CVE-2023-22458) Integer overflow in the Redis HRANDFIELD and ZRANDMEMBER
|
||||
commands can lead to denial-of-service
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Avoid possible hang when client issues long KEYS, SRANDMEMBER, HRANDFIELD,
|
||||
and ZRANDMEMBER commands and gets disconnected by client output buffer limit (#11676)
|
||||
* Fix sentinel issue if replica changes IP (#11590)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.8 Released Mon 12 Dec 2022 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: MODERATE, Contains fixes for a few non-critical or unlikely bugs
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Optimize zset conversion on large ZRANGESTORE (#10789)
|
||||
|
||||
Module API changes
|
||||
==================
|
||||
|
||||
* Fix crash in CLIENT_CHANGE event, when the selected database is not 0 (#11500)
|
||||
* Fix RM_SetAbsExpire and RM_GetAbsExpire API registration (#11025, #8564)
|
||||
|
||||
Security improvements
|
||||
=====================
|
||||
|
||||
* Sentinel: avoid logging auth-pass value (#9652)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Fix a crash when a Lua script returns a meta-table (#11032)
|
||||
* Fix ZRANGESTORE crash when zset_max_listpack_entries is 0 (#10767)
|
||||
* Unpause clients after manual failover ends instead of waiting for timed (#9676)
|
||||
* TLS: Notify clients on connection shutdown (#10931)
|
||||
* Avoid hang of diskless replication fork child when parent crashes (#11463)
|
||||
* Fix sentinel function that compares hostnames if failed resolve (#11419)
|
||||
* Fix a hang when eviction is combined with lazy-free and maxmemory-eviction-tenacity
|
||||
is set to 100 (#11237)
|
||||
* Fix bug with scripts ignoring client tracking NOLOOP (#11052)
|
||||
* Fix client-side tracking breaking protocol when FLUSHDB / FLUSHALL / SWAPDB is
|
||||
used inside MULTI-EXEC (#11038)
|
||||
* Fix BITFIELD overflow detection on some compilers due to undefined behaviour (#9601)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.7 Released Wed Apr 27 12:00:00 IDT 2022
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2022-24736) An attacker attempting to load a specially crafted Lua script
|
||||
can cause NULL pointer dereference which will result with a crash of the
|
||||
redis-server process. This issue affects all versions of Redis.
|
||||
[reported by Aviv Yahav].
|
||||
* (CVE-2022-24735) By exploiting weaknesses in the Lua script execution
|
||||
environment, an attacker with access to Redis can inject Lua code that will
|
||||
execute with the (potentially higher) privileges of another Redis user.
|
||||
[reported by Aviv Yahav].
|
||||
|
||||
|
||||
Potentially Breaking Fixes
|
||||
==========================
|
||||
|
||||
* LPOP/RPOP with count against non-existing list return null array (#10095)
|
||||
* LPOP/RPOP used to produce wrong replies when count is 0 (#9692)
|
||||
|
||||
|
||||
Performance and resource utilization improvements
|
||||
=================================================
|
||||
|
||||
* Speed optimization in command execution pipeline (#10502)
|
||||
* Fix regression in Z[REV]RANGE commands (by-rank) introduced in Redis 6.2 (#10337)
|
||||
|
||||
|
||||
Platform / toolchain support related improvements
|
||||
=================================================
|
||||
|
||||
* Fix RSS metrics on NetBSD and OpenBSD (#10116, #10149)
|
||||
* Fix OpenSSL 3.0.x related issues (#10291)
|
||||
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
* Lua: Add checks for min-slave-* configs when evaluating Lua scripts (#10160)
|
||||
* Lua: fix crash on a script call with many arguments, a regression in v6.2.6 (#9809)
|
||||
* Tracking: Make invalidation messages always after command's reply (#9422)
|
||||
* Fix excessive stream trimming due to an overflow (#10068)
|
||||
* Add missed error counting for INFO errorstats (#9646)
|
||||
* Fix geo search bounding box check causing missing results (#10018)
|
||||
* Improve EXPIRE TTL overflow detection (#9839)
|
||||
* Modules: Fix thread safety violation when a module thread adds an error reply, broken in 6.2 (#10278)
|
||||
* Modules: Fix missing and duplicate error stats (#10278)
|
||||
* Module APIs: release clients blocked on module commands in cluster resharding
|
||||
and down state (#9483)
|
||||
* Sentinel: Fix memory leak with TLS (#9753)
|
||||
* Sentinel: Fix issues with hostname support (#10146)
|
||||
* Sentinel: Fix election failures on certain container environments (#10197)
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.6 Released Mon Oct 4 12:00:00 IDT 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Security Fixes:
|
||||
* (CVE-2021-41099) Integer to heap buffer overflow handling certain string
|
||||
commands and network payloads, when proto-max-bulk-len is manually configured
|
||||
to a non-default, very large value [reported by yiyuaner].
|
||||
* (CVE-2021-32762) Integer to heap buffer overflow issue in redis-cli and
|
||||
redis-sentinel parsing large multi-bulk replies on some older and less common
|
||||
platforms [reported by Microsoft Vulnerability Research].
|
||||
* (CVE-2021-32687) Integer to heap buffer overflow with intsets, when
|
||||
set-max-intset-entries is manually configured to a non-default, very large
|
||||
value [reported by Pawel Wieczorkiewicz, AWS].
|
||||
* (CVE-2021-32675) Denial Of Service when processing RESP request payloads with
|
||||
a large number of elements on many connections.
|
||||
* (CVE-2021-32672) Random heap reading issue with Lua Debugger [reported by
|
||||
Meir Shpilraien].
|
||||
* (CVE-2021-32628) Integer to heap buffer overflow handling ziplist-encoded
|
||||
data types, when configuring a large, non-default value for
|
||||
hash-max-ziplist-entries, hash-max-ziplist-value, zset-max-ziplist-entries
|
||||
or zset-max-ziplist-value [reported by sundb].
|
||||
* (CVE-2021-32627) Integer to heap buffer overflow issue with streams, when
|
||||
configuring a non-default, large value for proto-max-bulk-len and
|
||||
client-query-buffer-limit [reported by sundb].
|
||||
* (CVE-2021-32626) Specially crafted Lua scripts may result with Heap buffer
|
||||
overflow [reported by Meir Shpilraien].
|
||||
|
||||
Bug fixes that involve behavior changes:
|
||||
* GEO* STORE with empty source key deletes the destination key and return 0 (#9271)
|
||||
Previously it would have returned an empty array like the non-STORE variant.
|
||||
* PUBSUB NUMPAT replies with number of patterns rather than number of subscriptions (#9209)
|
||||
This actually changed in 6.2.0 but was overlooked and omitted from the release notes.
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fix CLIENT PAUSE, used an old timeout from previous PAUSE (#9477)
|
||||
* Fix CLIENT PAUSE in a replica would mess the replication offset (#9448)
|
||||
* Add some missing error statistics in INFO errorstats (#9328)
|
||||
|
||||
Other bug fixes:
|
||||
* Fix incorrect reply of COMMAND command key positions for MIGRATE command (#9455)
|
||||
* Fix appendfsync to always guarantee fsync before reply, on MacOS and FreeBSD (kqueue) (#9416)
|
||||
* Fix the wrong mis-detection of sync_file_range system call, affecting performance (#9371)
|
||||
|
||||
CLI tools:
|
||||
* When redis-cli received ASK response, it didn't handle it (#8930)
|
||||
|
||||
Improvements:
|
||||
* Add latency monitor sample when key is deleted via lazy expire (#9317)
|
||||
* Sanitize corrupt payload improvements (#9321, #9399)
|
||||
* Delete empty keys when loading RDB file or handling a RESTORE command (#9297, #9349)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.5 Released Wed Jul 21 16:32:19 IDT 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues that affect
|
||||
authenticated client connections on 32-bit versions. MODERATE otherwise.
|
||||
|
||||
Fix integer overflow in BITFIELD on 32-bit versions (CVE-2021-32761).
|
||||
An integer overflow bug in Redis version 2.2 or newer can be exploited using the
|
||||
BITFIELD command to corrupt the heap and potentially result with remote code
|
||||
execution.
|
||||
|
||||
Bug fixes that involve behavior changes:
|
||||
* Change reply type for ZPOPMAX/MIN with count in RESP3 to nested array (#8981).
|
||||
Was using a flat array like in RESP2 instead of a nested array like ZRANGE does.
|
||||
* Fix reply type for HRANDFIELD and ZRANDMEMBER when key is missing (#9178).
|
||||
Was using a null array instead of an empty array.
|
||||
* Fix reply type for ZRANGESTORE when source key is missing (#9089).
|
||||
Was using an empty array like ZRANGE instead of 0 (used in the STORE variant).
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* ZRANDMEMBER WITHSCORES with negative COUNT may return bad score (#9162)
|
||||
* Fix crash after CLIENT UNPAUSE when threaded I/O config is enabled (#9041)
|
||||
* Fix XTRIM or XADD with LIMIT may delete more entries than the limit (#9048)
|
||||
* Fix build issue with OpenSSL 1.1.0 (#9233)
|
||||
|
||||
Other bug fixes:
|
||||
* Fail EXEC command in case a watched key is expired (#9194)
|
||||
* Fix SMOVE not to invalidate dest key (WATCH and tracking) when member already exists (#9244)
|
||||
* Fix SINTERSTORE not to delete dest key when getting a wrong type error (#9032)
|
||||
* Fix overflows on 32-bit versions in GETBIT, SETBIT, BITCOUNT, BITPOS, and BITFIELD (#9191)
|
||||
* Improve MEMORY USAGE on stream keys (#9164)
|
||||
* Set TCP keepalive on inbound cluster bus connections (#9230)
|
||||
* Fix diskless replica loading to recover from RDB short read on module AUX data (#9199)
|
||||
* Fix race in client side tracking (#9116)
|
||||
* Fix ziplist length updates on big-endian platforms (#2080)
|
||||
|
||||
CLI tools:
|
||||
* redis-cli cluster import command may issue wrong MIGRATE command, sending COPY instead of REPLACE (#8945)
|
||||
* redis-cli --rdb fixes when using "-" to write to stdout (#9136, #9135)
|
||||
* redis-cli support for RESP3 set type in CSV and RAW output (#7338)
|
||||
|
||||
Modules:
|
||||
* Module API for getting current command name (#8792)
|
||||
* Fix RM_StringTruncate when newlen is 0 (#3718)
|
||||
* Fix CLIENT UNBLOCK crashing modules without timeout callback (#9167)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.4 Released Tue June 1 12:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, Contains fixes to security issues that affect
|
||||
authenticated client connections. MODERATE otherwise.
|
||||
|
||||
Fix integer overflow in STRALGO LCS (CVE-2021-32625)
|
||||
An integer overflow bug in Redis version 6.0 or newer can be exploited using the
|
||||
STRALGO LCS command to corrupt the heap and potentially result with remote code
|
||||
execution. This is a result of an incomplete fix by CVE-2021-29477.
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fix crash after a diskless replication fork child is terminated (#8991)
|
||||
* Fix redis-benchmark crash on unsupported configs (#8916)
|
||||
|
||||
Other bug fixes:
|
||||
* Fix crash in UNLINK on a stream key with deleted consumer groups (#8932)
|
||||
* SINTERSTORE: Add missing keyspace del event when none of the sources exist (#8949)
|
||||
* Sentinel: Fix CONFIG SET of empty string sentinel-user/sentinel-pass configs (#8958)
|
||||
* Enforce client output buffer soft limit when no traffic (#8833)
|
||||
|
||||
Improvements:
|
||||
* Hide AUTH passwords in MIGRATE command from slowlog (#8859)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.3 Released Mon May 3 19:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY, Contains fixes to security issues that affect
|
||||
authenticated client connections. LOW otherwise.
|
||||
|
||||
Integer overflow in STRALGO LCS command (CVE-2021-29477):
|
||||
An integer overflow bug in Redis version 6.0 or newer could be exploited using
|
||||
the STRALGO LCS command to corrupt the heap and potentially result in remote
|
||||
code execution. The integer overflow bug exists in all versions of Redis
|
||||
starting with 6.0.
|
||||
|
||||
Integer overflow in COPY command for large intsets (CVE-2021-29478):
|
||||
An integer overflow bug in Redis 6.2 could be exploited to corrupt the heap and
|
||||
potentially result with remote code execution. The vulnerability involves
|
||||
changing the default set-max-intset-entries configuration value, creating a
|
||||
large set key that consists of integer values and using the COPY command to
|
||||
duplicate it. The integer overflow bug exists in all versions of Redis starting
|
||||
with 2.6, where it could result with a corrupted RDB or DUMP payload, but not
|
||||
exploited through COPY (which did not exist before 6.2).
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fix memory leak in moduleDefragGlobals (#8853)
|
||||
* Fix memory leak when doing lazy freeing client tracking table (#8822)
|
||||
* Block abusive replicas from sending command that could assert and crash redis (#8868)
|
||||
|
||||
Other bug fixes:
|
||||
* Use a monotonic clock to check for Lua script timeout (#8812)
|
||||
* redis-cli: Do not use unix socket when we got redirected in cluster mode (#8870)
|
||||
|
||||
Modules:
|
||||
* Fix RM_GetClusterNodeInfo() to correctly populate master id (#8846)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.2 Released Mon April 19 19:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: HIGH, if you're using ACL and pub/sub, CONFIG REWRITE, or
|
||||
suffering from performance regression. see below.
|
||||
|
||||
Bug fixes for regressions in previous releases of Redis 6.2:
|
||||
* Fix BGSAVE, AOFRW, and replication slowdown due to child reporting CoW (#8645)
|
||||
* Fix short busy loop when timer event is about to fire (#8764)
|
||||
* Fix default user, overwritten and reset users losing pubsub channel permissions (#8723)
|
||||
* Fix config rewrite with an empty `save` config resulsing in default `save` values (#8719)
|
||||
* Fix not starting on alpine/libmusl without IPv6 (#8655)
|
||||
* Fix issues with propagation and MULTI/EXEC in modules (#8617)
|
||||
Several issues around nested calls and thread safe contexts
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* ACL Pub/Sub channels permission handling for save/load scenario (#8794)
|
||||
* Fix early rejection of PUBLISH inside MULTI-EXEC transaction (#8534)
|
||||
* Fix missing SLOWLOG records for blocked commands (#8632)
|
||||
* Allow RESET command during busy scripts (#8629)
|
||||
* Fix some error replies were not counted on stats (#8659)
|
||||
|
||||
Bug fixes:
|
||||
* Add a timeout mechanism for replicas stuck in fullsync (#8762)
|
||||
* Process HELLO command even if the default user has no permissions (#8633)
|
||||
* Client issuing a long running script and using a pipeline, got disconnected (#8715)
|
||||
* Fix script kill to work also on scripts that use `pcall` (#8661)
|
||||
* Fix list-compress-depth may compress more node than required (#8311)
|
||||
* Fix redis-cli handling of rediss:// URL scheme (#8705)
|
||||
* Cluster: Skip unnecessary check which may prevent failure detection (#8585)
|
||||
* Cluster: Fix hang manual failover when replica just started (#8651)
|
||||
* Sentinel: Fix info-refresh time field before sentinel get first response (#8567)
|
||||
* Sentinel: Fix possible crash on failed connection attempt (#8627)
|
||||
* Systemd: Send the readiness notification when a replica is ready to accept connections (#8409)
|
||||
|
||||
Command behavior changes:
|
||||
* ZADD: fix wrong reply when INCR used with GT/LT which blocked the update (#8717)
|
||||
It was responding with the incremented value rather than nil
|
||||
* XAUTOCLAIM: fix response to return the next available id as the cursor (#8725)
|
||||
Previous behavior was retuning the last one which was already scanned
|
||||
* XAUTOCLAIM: fix JUSTID to prevent incrementing delivery_count (#8724)
|
||||
|
||||
New config options:
|
||||
* Add cluster-allow-replica-migration config option (#5285)
|
||||
* Add replica-announced config option (#8653)
|
||||
* Add support for plaintext clients in TLS cluster (#8587)
|
||||
* Add support for reading encrypted keyfiles (#8644)
|
||||
|
||||
Improvements:
|
||||
* Fix performance regression in BRPOP on Redis 6.0 (#8689)
|
||||
* Avoid adding slowlog entries for config with sensitive data (#8584)
|
||||
* Improve redis-cli non-binary safe string handling (#8566)
|
||||
* Optimize CLUSTER SLOTS reply (#8541)
|
||||
* Handle remaining fsync errors (#8419)
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Strip % sign from current_fork_perc info field (#8628)
|
||||
* Fix RSS memory info on FreeBSD (#8620)
|
||||
* Fix client_recent_max_input/output_buffer in 'INFO CLIENTS' when all clients drop (#8588)
|
||||
* Fix invalid master_link_down_since_seconds in info replication (#8785)
|
||||
|
||||
Platform and deployment-related changes:
|
||||
* Fix FreeBSD <12.x builds (#8603)
|
||||
|
||||
Modules:
|
||||
* Add macros for RedisModule_log logging levels (#4246)
|
||||
* Add RedisModule_GetAbsExpire / RedisModule_SetAbsExpire (#8564)
|
||||
* Add a module type for key space notification (#8759)
|
||||
* Set module eviction context flag only in masters (#8631)
|
||||
* Fix unusable RedisModule_IsAOFClient API (#8596)
|
||||
* Fix missing EXEC on modules propagation after failed EVAL execution (#8654)
|
||||
* Fix edge-case when a module client is unblocked (#8618)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.1 Released Mon Mar 1 17:51:36 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: LOW.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2.0,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
Bug fixes:
|
||||
* Fix sanitize-dump-payload for stream with deleted records (#8568)
|
||||
* Prevent client-query-buffer-limit config from being set to lower than 1mb (#8557)
|
||||
|
||||
Improvements:
|
||||
* Make port, tls-port and bind config options modifiable at runtime (#8510)
|
||||
|
||||
Platform and deployment-related changes:
|
||||
* Fix compilation error on non-glibc systems if jemalloc is not used (#8533)
|
||||
* Improved memory consumption and memory usage tracking on FreeBSD (#8545)
|
||||
* Fix compilation on ARM64 MacOS with jemalloc (#8458)
|
||||
|
||||
Modules:
|
||||
* New Module API for getting user name of a client (#8508)
|
||||
* Optimize RM_Call by utilizing a shared reusable client (#8516)
|
||||
* Fix crash running CLIENT INFO via RM_Call (#8560)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2.0 GA Released Tue Feb 22 14:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
@@ -15,10 +15,10 @@ Another good example is to think of Redis as a more complex version of memcached
|
||||
|
||||
If you want to know more, this is a list of selected starting points:
|
||||
|
||||
* Introduction to Redis data types. http://redis.io/topics/data-types-intro
|
||||
* Introduction to Redis data types. https://redis.io/topics/data-types-intro
|
||||
* Try Redis directly inside your browser. http://try.redis.io
|
||||
* The full list of Redis commands. http://redis.io/commands
|
||||
* There is much more inside the official Redis documentation. http://redis.io/documentation
|
||||
* The full list of Redis commands. https://redis.io/commands
|
||||
* There is much more inside the official Redis documentation. https://redis.io/documentation
|
||||
|
||||
Building Redis
|
||||
--------------
|
||||
@@ -49,7 +49,7 @@ To append a suffix to Redis program names, use:
|
||||
|
||||
% make PROG_SUFFIX="-alt"
|
||||
|
||||
You can run a 32 bit Redis binary using:
|
||||
You can build a 32 bit Redis binary using:
|
||||
|
||||
% make 32bit
|
||||
|
||||
@@ -184,7 +184,7 @@ then in another terminal try the following:
|
||||
(integer) 2
|
||||
redis>
|
||||
|
||||
You can find the list of all the available commands at http://redis.io/commands.
|
||||
You can find the list of all the available commands at https://redis.io/commands.
|
||||
|
||||
Installing Redis
|
||||
-----------------
|
||||
@@ -294,19 +294,19 @@ the structure definition.
|
||||
Another important Redis data structure is the one defining a client.
|
||||
In the past it was called `redisClient`, now just `client`. The structure
|
||||
has many fields, here we'll just show the main ones:
|
||||
|
||||
struct client {
|
||||
int fd;
|
||||
sds querybuf;
|
||||
int argc;
|
||||
robj **argv;
|
||||
redisDb *db;
|
||||
int flags;
|
||||
list *reply;
|
||||
char buf[PROTO_REPLY_CHUNK_BYTES];
|
||||
... many other fields ...
|
||||
}
|
||||
|
||||
```c
|
||||
struct client {
|
||||
int fd;
|
||||
sds querybuf;
|
||||
int argc;
|
||||
robj **argv;
|
||||
redisDb *db;
|
||||
int flags;
|
||||
list *reply;
|
||||
char buf[PROTO_REPLY_CHUNK_BYTES];
|
||||
// ... many other fields ...
|
||||
}
|
||||
```
|
||||
The client structure defines a *connected client*:
|
||||
|
||||
* The `fd` field is the client socket file descriptor.
|
||||
@@ -453,7 +453,7 @@ Other C files
|
||||
* `scripting.c` implements Lua scripting. It is completely self-contained and isolated from the rest of the Redis implementation and is simple enough to understand if you are familiar with the Lua API.
|
||||
* `cluster.c` implements the Redis Cluster. Probably a good read only after being very familiar with the rest of the Redis code base. If you want to read `cluster.c` make sure to read the [Redis Cluster specification][3].
|
||||
|
||||
[3]: http://redis.io/topics/cluster-spec
|
||||
[3]: https://redis.io/topics/cluster-spec
|
||||
|
||||
Anatomy of a Redis command
|
||||
---
|
||||
|
||||
Vendored
+7
@@ -2,6 +2,8 @@
|
||||
|
||||
uname_S:= $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
LUA_COVERAGE?=no
|
||||
|
||||
CCCOLOR="\033[34m"
|
||||
LINKCOLOR="\033[34;1m"
|
||||
SRCCOLOR="\033[33m"
|
||||
@@ -71,6 +73,11 @@ endif
|
||||
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' -DLUA_USE_MKSTEMP $(CFLAGS)
|
||||
LUA_LDFLAGS+= $(LDFLAGS)
|
||||
ifeq ($(LUA_COVERAGE),yes)
|
||||
LUA_CFLAGS += -fprofile-arcs -ftest-coverage
|
||||
LUA_LDFLAGS += -fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
|
||||
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
|
||||
# challenging to cross-compile lua (and redis). These defines make it easier
|
||||
# to fit redis into cross-compilation environments, which typically set AR.
|
||||
|
||||
Vendored
+6
-1
@@ -77,7 +77,12 @@ ifeq ($(USE_SSL),1)
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),Linux)
|
||||
SSL_LDFLAGS=-lssl -lcrypto
|
||||
ifdef OPENSSL_PREFIX
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
|
||||
else
|
||||
SSL_LDFLAGS=-lssl -lcrypto
|
||||
endif
|
||||
else
|
||||
OPENSSL_PREFIX?=/usr/local/opt/openssl
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
|
||||
Vendored
+4
@@ -68,6 +68,10 @@ void *hi_malloc(size_t size) {
|
||||
}
|
||||
|
||||
void *hi_calloc(size_t nmemb, size_t size) {
|
||||
/* Overflow check as the user can specify any arbitrary allocator */
|
||||
if (SIZE_MAX / size < nmemb)
|
||||
return NULL;
|
||||
|
||||
return hiredisAllocFns.callocFn(nmemb, size);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+5
@@ -32,6 +32,7 @@
|
||||
#define HIREDIS_ALLOC_H
|
||||
|
||||
#include <stddef.h> /* for size_t */
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -59,6 +60,10 @@ static inline void *hi_malloc(size_t size) {
|
||||
}
|
||||
|
||||
static inline void *hi_calloc(size_t nmemb, size_t size) {
|
||||
/* Overflow check as the user can specify any arbitrary allocator */
|
||||
if (SIZE_MAX / size < nmemb)
|
||||
return NULL;
|
||||
|
||||
return hiredisAllocFns.callocFn(nmemb, size);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+14
@@ -498,6 +498,20 @@ static void test_reply_reader(void) {
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Multi-bulk never overflows regardless of maxelements: ");
|
||||
size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3;
|
||||
char bad_mbulk_reply[100];
|
||||
snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n",
|
||||
(unsigned long long) bad_mbulk_len);
|
||||
|
||||
reader = redisReaderCreate();
|
||||
reader->maxelements = 0; /* Don't rely on default limit */
|
||||
redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply));
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when array > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
|
||||
@@ -235,7 +235,7 @@ iget_defrag_hint(tsdn_t *tsdn, void* ptr) {
|
||||
int free_in_slab = extent_nfree_get(slab);
|
||||
if (free_in_slab) {
|
||||
const bin_info_t *bin_info = &bin_infos[binind];
|
||||
int curslabs = bin->stats.curslabs;
|
||||
unsigned long curslabs = bin->stats.curslabs;
|
||||
size_t curregs = bin->stats.curregs;
|
||||
if (bin->slabcur) {
|
||||
/* remove slabcur from the overall utilization */
|
||||
|
||||
Vendored
+22
@@ -674,6 +674,8 @@ LUA_API void lua_rawset (lua_State *L, int idx) {
|
||||
api_checknelems(L, 2);
|
||||
t = index2adr(L, idx);
|
||||
api_check(L, ttistable(t));
|
||||
if (hvalue(t)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1);
|
||||
luaC_barriert(L, hvalue(t), L->top-1);
|
||||
L->top -= 2;
|
||||
@@ -687,6 +689,8 @@ LUA_API void lua_rawseti (lua_State *L, int idx, int n) {
|
||||
api_checknelems(L, 1);
|
||||
o = index2adr(L, idx);
|
||||
api_check(L, ttistable(o));
|
||||
if (hvalue(o)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
setobj2t(L, luaH_setnum(L, hvalue(o), n), L->top-1);
|
||||
luaC_barriert(L, hvalue(o), L->top-1);
|
||||
L->top--;
|
||||
@@ -709,6 +713,8 @@ LUA_API int lua_setmetatable (lua_State *L, int objindex) {
|
||||
}
|
||||
switch (ttype(obj)) {
|
||||
case LUA_TTABLE: {
|
||||
if (hvalue(obj)->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
hvalue(obj)->metatable = mt;
|
||||
if (mt)
|
||||
luaC_objbarriert(L, hvalue(obj), mt);
|
||||
@@ -1085,3 +1091,19 @@ LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) {
|
||||
return name;
|
||||
}
|
||||
|
||||
LUA_API void lua_enablereadonlytable (lua_State *L, int objindex, int enabled) {
|
||||
const TValue* o = index2adr(L, objindex);
|
||||
api_check(L, ttistable(o));
|
||||
Table* t = hvalue(o);
|
||||
api_check(L, t != hvalue(registry(L)));
|
||||
t->readonly = enabled;
|
||||
}
|
||||
|
||||
LUA_API int lua_isreadonlytable (lua_State *L, int objindex) {
|
||||
const TValue* o = index2adr(L, objindex);
|
||||
api_check(L, ttistable(o));
|
||||
Table* t = hvalue(o);
|
||||
api_check(L, t != hvalue(registry(L)));
|
||||
return t->readonly;
|
||||
}
|
||||
|
||||
|
||||
Vendored
-1
@@ -80,7 +80,6 @@ LUA_API int lua_gethookcount (lua_State *L) {
|
||||
return L->basehookcount;
|
||||
}
|
||||
|
||||
|
||||
LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) {
|
||||
int status;
|
||||
CallInfo *ci;
|
||||
|
||||
Vendored
+2
-1
@@ -337,7 +337,8 @@ typedef struct Node {
|
||||
|
||||
typedef struct Table {
|
||||
CommonHeader;
|
||||
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
|
||||
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
|
||||
int readonly;
|
||||
lu_byte lsizenode; /* log2 of size of `node' array */
|
||||
struct Table *metatable;
|
||||
TValue *array; /* array part */
|
||||
|
||||
Vendored
+1
@@ -364,6 +364,7 @@ Table *luaH_new (lua_State *L, int narray, int nhash) {
|
||||
t->array = NULL;
|
||||
t->sizearray = 0;
|
||||
t->lsizenode = 0;
|
||||
t->readonly = 0;
|
||||
t->node = cast(Node *, dummynode);
|
||||
setarrayvector(L, t, narray);
|
||||
setnodevector(L, t, nhash);
|
||||
|
||||
Vendored
+3
@@ -358,6 +358,9 @@ struct lua_Debug {
|
||||
int i_ci; /* active function */
|
||||
};
|
||||
|
||||
LUA_API void lua_enablereadonlytable (lua_State *L, int index, int enabled);
|
||||
LUA_API int lua_isreadonlytable (lua_State *L, int objindex);
|
||||
|
||||
/* }====================================================================== */
|
||||
|
||||
|
||||
|
||||
Vendored
+1
@@ -131,6 +131,7 @@ static int bit_tohex(lua_State *L)
|
||||
const char *hexdigits = "0123456789abcdef";
|
||||
char buf[8];
|
||||
int i;
|
||||
if (n == INT32_MIN) n = INT32_MIN+1;
|
||||
if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; }
|
||||
if (n > 8) n = 8;
|
||||
for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; }
|
||||
|
||||
Vendored
+6
-3
@@ -39,6 +39,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
@@ -141,13 +142,13 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
json_token_type_t type;
|
||||
int index;
|
||||
size_t index;
|
||||
union {
|
||||
const char *string;
|
||||
double number;
|
||||
int boolean;
|
||||
} value;
|
||||
int string_len;
|
||||
size_t string_len;
|
||||
} json_token_t;
|
||||
|
||||
static const char *char2escape[256] = {
|
||||
@@ -473,6 +474,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
|
||||
* This buffer is reused constantly for small strings
|
||||
* If there are any excess pages, they won't be hit anyway.
|
||||
* This gains ~5% speedup. */
|
||||
if (len > SIZE_MAX / 6 - 3)
|
||||
abort(); /* Overflow check */
|
||||
strbuf_ensure_empty_length(json, len * 6 + 2);
|
||||
|
||||
strbuf_append_char_unsafe(json, '\"');
|
||||
@@ -706,7 +709,7 @@ static int json_encode(lua_State *l)
|
||||
strbuf_t local_encode_buf;
|
||||
strbuf_t *encode_buf;
|
||||
char *json;
|
||||
int len;
|
||||
size_t len;
|
||||
|
||||
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
|
||||
|
||||
|
||||
Vendored
+17
-14
@@ -117,7 +117,9 @@ mp_buf *mp_buf_new(lua_State *L) {
|
||||
|
||||
void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
if (buf->free < len) {
|
||||
size_t newsize = (buf->len+len)*2;
|
||||
size_t newsize = buf->len+len;
|
||||
if (newsize < buf->len || newsize >= SIZE_MAX/2) abort();
|
||||
newsize *= 2;
|
||||
|
||||
buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize);
|
||||
buf->free = newsize - buf->len;
|
||||
@@ -173,7 +175,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
|
||||
void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
unsigned char hdr[5];
|
||||
int hdrlen;
|
||||
size_t hdrlen;
|
||||
|
||||
if (len < 32) {
|
||||
hdr[0] = 0xa0 | (len&0xff); /* fix raw */
|
||||
@@ -220,7 +222,7 @@ void mp_encode_double(lua_State *L, mp_buf *buf, double d) {
|
||||
|
||||
void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
unsigned char b[9];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n >= 0) {
|
||||
if (n <= 127) {
|
||||
@@ -290,9 +292,9 @@ void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n <= 15) {
|
||||
b[0] = 0x90 | (n & 0xf); /* fix array */
|
||||
@@ -313,7 +315,7 @@ void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -790,7 +792,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
}
|
||||
}
|
||||
|
||||
int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
int mp_unpack_full(lua_State *L, lua_Integer limit, lua_Integer offset) {
|
||||
size_t len;
|
||||
const char *s;
|
||||
mp_cur c;
|
||||
@@ -802,10 +804,10 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */
|
||||
return luaL_error(L,
|
||||
"Invalid request to unpack with offset of %d and limit of %d.",
|
||||
offset, len);
|
||||
(int) offset, (int) len);
|
||||
else if (offset > len)
|
||||
return luaL_error(L,
|
||||
"Start offset %d greater than input length %d.", offset, len);
|
||||
"Start offset %d greater than input length %d.", (int) offset, (int) len);
|
||||
|
||||
if (decode_all) limit = INT_MAX;
|
||||
|
||||
@@ -827,12 +829,13 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
/* c->left is the remaining size of the input buffer.
|
||||
* subtract the entire buffer size from the unprocessed size
|
||||
* to get our next start offset */
|
||||
int offset = len - c.left;
|
||||
size_t new_offset = len - c.left;
|
||||
if (new_offset > LONG_MAX) abort();
|
||||
|
||||
luaL_checkstack(L, 1, "in function mp_unpack_full");
|
||||
|
||||
/* Return offset -1 when we have have processed the entire buffer. */
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : offset);
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : (lua_Integer) new_offset);
|
||||
/* Results are returned with the arg elements still
|
||||
* in place. Lua takes care of only returning
|
||||
* elements above the args for us.
|
||||
@@ -851,15 +854,15 @@ int mp_unpack(lua_State *L) {
|
||||
}
|
||||
|
||||
int mp_unpack_one(lua_State *L) {
|
||||
int offset = luaL_optinteger(L, 2, 0);
|
||||
lua_Integer offset = luaL_optinteger(L, 2, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
return mp_unpack_full(L, 1, offset);
|
||||
}
|
||||
|
||||
int mp_unpack_limit(lua_State *L) {
|
||||
int limit = luaL_checkinteger(L, 2);
|
||||
int offset = luaL_optinteger(L, 3, 0);
|
||||
lua_Integer limit = luaL_checkinteger(L, 2);
|
||||
lua_Integer offset = luaL_optinteger(L, 3, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
|
||||
|
||||
Vendored
+2
@@ -138,6 +138,8 @@ void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
|
||||
const TValue *tm;
|
||||
if (ttistable(t)) { /* `t' is a table? */
|
||||
Table *h = hvalue(t);
|
||||
if (h->readonly)
|
||||
luaG_runerror(L, "Attempt to modify a readonly table");
|
||||
TValue *oldval = luaH_set(L, h, key); /* do a primitive set */
|
||||
if (!ttisnil(oldval) || /* result is no nil? */
|
||||
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
|
||||
|
||||
Vendored
+28
-81
@@ -26,6 +26,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
@@ -38,22 +39,22 @@ static void die(const char *fmt, ...)
|
||||
va_end(arg);
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
exit(-1);
|
||||
abort();
|
||||
}
|
||||
|
||||
void strbuf_init(strbuf_t *s, int len)
|
||||
void strbuf_init(strbuf_t *s, size_t len)
|
||||
{
|
||||
int size;
|
||||
size_t size;
|
||||
|
||||
if (len <= 0)
|
||||
if (!len)
|
||||
size = STRBUF_DEFAULT_SIZE;
|
||||
else
|
||||
size = len + 1; /* \0 terminator */
|
||||
|
||||
size = len + 1;
|
||||
if (size < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
s->buf = NULL;
|
||||
s->size = size;
|
||||
s->length = 0;
|
||||
s->increment = STRBUF_DEFAULT_INCREMENT;
|
||||
s->dynamic = 0;
|
||||
s->reallocs = 0;
|
||||
s->debug = 0;
|
||||
@@ -65,7 +66,7 @@ void strbuf_init(strbuf_t *s, int len)
|
||||
strbuf_ensure_null(s);
|
||||
}
|
||||
|
||||
strbuf_t *strbuf_new(int len)
|
||||
strbuf_t *strbuf_new(size_t len)
|
||||
{
|
||||
strbuf_t *s;
|
||||
|
||||
@@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len)
|
||||
return s;
|
||||
}
|
||||
|
||||
void strbuf_set_increment(strbuf_t *s, int increment)
|
||||
{
|
||||
/* Increment > 0: Linear buffer growth rate
|
||||
* Increment < -1: Exponential buffer growth rate */
|
||||
if (increment == 0 || increment == -1)
|
||||
die("BUG: Invalid string increment");
|
||||
|
||||
s->increment = increment;
|
||||
}
|
||||
|
||||
static inline void debug_stats(strbuf_t *s)
|
||||
{
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n",
|
||||
(long)s, s->reallocs, s->length, s->size);
|
||||
}
|
||||
}
|
||||
@@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s)
|
||||
free(s);
|
||||
}
|
||||
|
||||
char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
char *strbuf_free_to_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
@@ -131,57 +122,62 @@ char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int calculate_new_size(strbuf_t *s, int len)
|
||||
static size_t calculate_new_size(strbuf_t *s, size_t len)
|
||||
{
|
||||
int reqsize, newsize;
|
||||
size_t reqsize, newsize;
|
||||
|
||||
if (len <= 0)
|
||||
die("BUG: Invalid strbuf length requested");
|
||||
|
||||
/* Ensure there is room for optional NULL termination */
|
||||
reqsize = len + 1;
|
||||
if (reqsize < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
|
||||
/* If the user has requested to shrink the buffer, do it exactly */
|
||||
if (s->size > reqsize)
|
||||
return reqsize;
|
||||
|
||||
newsize = s->size;
|
||||
if (s->increment < 0) {
|
||||
if (reqsize >= SIZE_MAX / 2) {
|
||||
newsize = reqsize;
|
||||
} else {
|
||||
/* Exponential sizing */
|
||||
while (newsize < reqsize)
|
||||
newsize *= -s->increment;
|
||||
} else {
|
||||
/* Linear sizing */
|
||||
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
|
||||
newsize *= 2;
|
||||
}
|
||||
|
||||
if (newsize < reqsize)
|
||||
die("BUG: strbuf length would overflow, len: %zu", len);
|
||||
|
||||
return newsize;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure strbuf can handle a string length bytes long (ignoring NULL
|
||||
* optional termination). */
|
||||
void strbuf_resize(strbuf_t *s, int len)
|
||||
void strbuf_resize(strbuf_t *s, size_t len)
|
||||
{
|
||||
int newsize;
|
||||
size_t newsize;
|
||||
|
||||
newsize = calculate_new_size(s, len);
|
||||
|
||||
if (s->debug > 1) {
|
||||
fprintf(stderr, "strbuf(%lx) resize: %d => %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n",
|
||||
(long)s, s->size, newsize);
|
||||
}
|
||||
|
||||
s->size = newsize;
|
||||
s->buf = realloc(s->buf, s->size);
|
||||
if (!s->buf)
|
||||
die("Out of memory");
|
||||
die("Out of memory, len: %zu", len);
|
||||
s->reallocs++;
|
||||
}
|
||||
|
||||
void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
{
|
||||
int space, i;
|
||||
int i;
|
||||
size_t space;
|
||||
|
||||
space = strbuf_empty_length(s);
|
||||
|
||||
@@ -197,55 +193,6 @@ void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
}
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt() should only be used when an upper bound
|
||||
* is known for the output string. */
|
||||
void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len;
|
||||
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
|
||||
va_start(arg, fmt);
|
||||
fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len < 0)
|
||||
die("BUG: Unable to convert number"); /* This should never happen.. */
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt_retry() can be used when the there is no known
|
||||
* upper bound for the output string. */
|
||||
void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len, try;
|
||||
int empty_len;
|
||||
|
||||
/* If the first attempt to append fails, resize the buffer appropriately
|
||||
* and try again */
|
||||
for (try = 0; ; try++) {
|
||||
va_start(arg, fmt);
|
||||
/* Append the new formatted string */
|
||||
/* fmt_len is the length of the string required, excluding the
|
||||
* trailing NULL */
|
||||
empty_len = strbuf_empty_length(s);
|
||||
/* Add 1 since there is also space to store the terminating NULL. */
|
||||
fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len <= empty_len)
|
||||
break; /* SUCCESS */
|
||||
if (try > 0)
|
||||
die("BUG: length of formatted string changed");
|
||||
|
||||
strbuf_resize(s, s->length + fmt_len);
|
||||
}
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* vi:ai et sw=4 ts=4:
|
||||
*/
|
||||
|
||||
Vendored
+19
-27
@@ -27,15 +27,13 @@
|
||||
|
||||
/* Size: Total bytes allocated to *buf
|
||||
* Length: String length, excluding optional NULL terminator.
|
||||
* Increment: Allocation increments when resizing the string buffer.
|
||||
* Dynamic: True if created via strbuf_new()
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
int size;
|
||||
int length;
|
||||
int increment;
|
||||
size_t size;
|
||||
size_t length;
|
||||
int dynamic;
|
||||
int reallocs;
|
||||
int debug;
|
||||
@@ -44,32 +42,26 @@ typedef struct {
|
||||
#ifndef STRBUF_DEFAULT_SIZE
|
||||
#define STRBUF_DEFAULT_SIZE 1023
|
||||
#endif
|
||||
#ifndef STRBUF_DEFAULT_INCREMENT
|
||||
#define STRBUF_DEFAULT_INCREMENT -2
|
||||
#endif
|
||||
|
||||
/* Initialise */
|
||||
extern strbuf_t *strbuf_new(int len);
|
||||
extern void strbuf_init(strbuf_t *s, int len);
|
||||
extern void strbuf_set_increment(strbuf_t *s, int increment);
|
||||
extern strbuf_t *strbuf_new(size_t len);
|
||||
extern void strbuf_init(strbuf_t *s, size_t len);
|
||||
|
||||
/* Release */
|
||||
extern void strbuf_free(strbuf_t *s);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, int *len);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, size_t *len);
|
||||
|
||||
/* Management */
|
||||
extern void strbuf_resize(strbuf_t *s, int len);
|
||||
static int strbuf_empty_length(strbuf_t *s);
|
||||
static int strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, int *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
|
||||
extern void strbuf_resize(strbuf_t *s, size_t len);
|
||||
static size_t strbuf_empty_length(strbuf_t *s);
|
||||
static size_t strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, size_t *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, size_t len);
|
||||
static char *strbuf_empty_ptr(strbuf_t *s);
|
||||
static void strbuf_extend_length(strbuf_t *s, int len);
|
||||
static void strbuf_extend_length(strbuf_t *s, size_t len);
|
||||
|
||||
/* Update */
|
||||
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
|
||||
extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len);
|
||||
extern void strbuf_append_string(strbuf_t *s, const char *str);
|
||||
static void strbuf_append_char(strbuf_t *s, const char c);
|
||||
static void strbuf_ensure_null(strbuf_t *s);
|
||||
@@ -87,12 +79,12 @@ static inline int strbuf_allocated(strbuf_t *s)
|
||||
|
||||
/* Return bytes remaining in the string buffer
|
||||
* Ensure there is space for a NULL terminator. */
|
||||
static inline int strbuf_empty_length(strbuf_t *s)
|
||||
static inline size_t strbuf_empty_length(strbuf_t *s)
|
||||
{
|
||||
return s->size - s->length - 1;
|
||||
}
|
||||
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
if (len > strbuf_empty_length(s))
|
||||
strbuf_resize(s, s->length + len);
|
||||
@@ -103,12 +95,12 @@ static inline char *strbuf_empty_ptr(strbuf_t *s)
|
||||
return s->buf + s->length;
|
||||
}
|
||||
|
||||
static inline void strbuf_extend_length(strbuf_t *s, int len)
|
||||
static inline void strbuf_extend_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline int strbuf_length(strbuf_t *s)
|
||||
static inline size_t strbuf_length(strbuf_t *s)
|
||||
{
|
||||
return s->length;
|
||||
}
|
||||
@@ -124,14 +116,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
|
||||
s->buf[s->length++] = c;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
@@ -142,7 +134,7 @@ static inline void strbuf_ensure_null(strbuf_t *s)
|
||||
s->buf[s->length] = 0;
|
||||
}
|
||||
|
||||
static inline char *strbuf_string(strbuf_t *s, int *len)
|
||||
static inline char *strbuf_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
if (len)
|
||||
*len = s->length;
|
||||
|
||||
+53
-13
@@ -150,6 +150,11 @@ tcp-keepalive 300
|
||||
#
|
||||
# tls-cert-file redis.crt
|
||||
# tls-key-file redis.key
|
||||
#
|
||||
# If the key file is encrypted using a passphrase, it can be included here
|
||||
# as well.
|
||||
#
|
||||
# tls-key-file-pass secret
|
||||
|
||||
# Normally Redis uses the same certificate for both server functions (accepting
|
||||
# connections) and client functions (replicating from a master, establishing
|
||||
@@ -162,8 +167,15 @@ tcp-keepalive 300
|
||||
#
|
||||
# tls-client-cert-file client.crt
|
||||
# tls-client-key-file client.key
|
||||
#
|
||||
# If the key file is encrypted using a passphrase, it can be included here
|
||||
# as well.
|
||||
#
|
||||
# tls-client-key-file-pass secret
|
||||
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange,
|
||||
# required by older versions of OpenSSL (<3.0). Newer versions do not require
|
||||
# this configuration and recommend against it.
|
||||
#
|
||||
# tls-dh-params-file redis.dh
|
||||
|
||||
@@ -657,6 +669,18 @@ repl-disable-tcp-nodelay no
|
||||
# By default the priority is 100.
|
||||
replica-priority 100
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# By default, Redis Sentinel includes all replicas in its reports. A replica
|
||||
# can be excluded from Redis Sentinel's announcements. An unannounced replica
|
||||
# will be ignored by the 'sentinel replicas <master>' command and won't be
|
||||
# exposed to Redis Sentinel's clients.
|
||||
#
|
||||
# This option does not change the behavior of replica-priority. Even with
|
||||
# replica-announced set to 'no', the replica can be promoted to master. To
|
||||
# prevent this behavior, set replica-priority to 0.
|
||||
#
|
||||
# replica-announced yes
|
||||
|
||||
# It is possible for a master to stop accepting writes if there are less than
|
||||
# N replicas connected, having a lag less or equal than M seconds.
|
||||
#
|
||||
@@ -895,7 +919,7 @@ acllog-max-len 128
|
||||
# order to provide better out-of-the-box Pub/Sub security. Therefore, it is
|
||||
# recommended that you explicitly define Pub/Sub permissions for all users
|
||||
# rather then rely on implicit default values. Once you've set explicit
|
||||
# Pub/Sub for all exisitn users, you should uncomment the following line.
|
||||
# Pub/Sub for all existing users, you should uncomment the following line.
|
||||
#
|
||||
# acl-pubsub-default resetchannels
|
||||
|
||||
@@ -1225,7 +1249,7 @@ disable-thp yes
|
||||
# If the AOF is enabled on startup Redis will load the AOF, that is the file
|
||||
# with the better durability guarantees.
|
||||
#
|
||||
# Please check http://redis.io/topics/persistence for more information.
|
||||
# Please check https://redis.io/topics/persistence for more information.
|
||||
|
||||
appendonly no
|
||||
|
||||
@@ -1434,12 +1458,21 @@ lua-time-limit 5000
|
||||
# master in your cluster.
|
||||
#
|
||||
# Default is 1 (replicas migrate only if their masters remain with at least
|
||||
# one replica). To disable migration just set it to a very large value.
|
||||
# one replica). To disable migration just set it to a very large value or
|
||||
# set cluster-allow-replica-migration to 'no'.
|
||||
# A value of 0 can be set but is useful only for debugging and dangerous
|
||||
# in production.
|
||||
#
|
||||
# cluster-migration-barrier 1
|
||||
|
||||
# Turning off this option allows to use less automatic cluster configuration.
|
||||
# It both disables migration to orphaned masters and migration from masters
|
||||
# that became empty.
|
||||
#
|
||||
# Default is 'yes' (allow automatic migrations).
|
||||
#
|
||||
# cluster-allow-replica-migration yes
|
||||
|
||||
# By default Redis Cluster nodes stop accepting queries if they detect there
|
||||
# is at least a hash slot uncovered (no available node is serving it).
|
||||
# This way if the cluster is partially down (for example a range of hash slots
|
||||
@@ -1480,7 +1513,7 @@ lua-time-limit 5000
|
||||
# cluster-allow-reads-when-down no
|
||||
|
||||
# In order to setup your cluster make sure to read the documentation
|
||||
# available at http://redis.io web site.
|
||||
# available at https://redis.io web site.
|
||||
|
||||
########################## CLUSTER DOCKER/NAT support ########################
|
||||
|
||||
@@ -1490,16 +1523,21 @@ lua-time-limit 5000
|
||||
#
|
||||
# In order to make Redis Cluster working in such environments, a static
|
||||
# configuration where each node knows its public address is needed. The
|
||||
# following two options are used for this scope, and are:
|
||||
# following four options are used for this scope, and are:
|
||||
#
|
||||
# * cluster-announce-ip
|
||||
# * cluster-announce-port
|
||||
# * cluster-announce-tls-port
|
||||
# * cluster-announce-bus-port
|
||||
#
|
||||
# Each instructs the node about its address, client port, and cluster message
|
||||
# bus port. The information is then published in the header of the bus packets
|
||||
# so that other nodes will be able to correctly map the address of the node
|
||||
# publishing the information.
|
||||
# Each instructs the node about its address, client ports (for connections
|
||||
# without and with TLS) and cluster message bus port. The information is then
|
||||
# published in the header of the bus packets so that other nodes will be able to
|
||||
# correctly map the address of the node publishing the information.
|
||||
#
|
||||
# If cluster-tls is set to yes and cluster-announce-tls-port is omitted or set
|
||||
# to zero, then cluster-announce-port refers to the TLS port. Note also that
|
||||
# cluster-announce-tls-port has no effect if cluster-tls is set to no.
|
||||
#
|
||||
# If the above options are not used, the normal Redis Cluster auto-detection
|
||||
# will be used instead.
|
||||
@@ -1512,7 +1550,8 @@ lua-time-limit 5000
|
||||
# Example:
|
||||
#
|
||||
# cluster-announce-ip 10.1.1.5
|
||||
# cluster-announce-port 6379
|
||||
# cluster-announce-tls-port 6379
|
||||
# cluster-announce-port 0
|
||||
# cluster-announce-bus-port 6380
|
||||
|
||||
################################## SLOW LOG ###################################
|
||||
@@ -1563,7 +1602,7 @@ latency-monitor-threshold 0
|
||||
############################# EVENT NOTIFICATION ##############################
|
||||
|
||||
# Redis can notify Pub/Sub clients about events happening in the key space.
|
||||
# This feature is documented at http://redis.io/topics/notifications
|
||||
# This feature is documented at https://redis.io/topics/notifications
|
||||
#
|
||||
# For instance if keyspace events notification is enabled, and a client
|
||||
# performs a DEL operation on key "foo" stored in the Database 0, two
|
||||
@@ -1586,8 +1625,9 @@ latency-monitor-threshold 0
|
||||
# x Expired events (events generated every time a key expires)
|
||||
# e Evicted events (events generated when a key is evicted for maxmemory)
|
||||
# t Stream commands
|
||||
# d Module key type events
|
||||
# m Key-miss events (Note: It is not included in the 'A' class)
|
||||
# A Alias for g$lshzxet, so that the "AKE" string means all the events
|
||||
# A Alias for g$lshzxetd, so that the "AKE" string means all the events
|
||||
# (Except key-miss events which are excluded from 'A' due to their
|
||||
# unique nature).
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
[ -z "$MAKE" ] && MAKE=make
|
||||
|
||||
@@ -16,6 +16,7 @@ fi
|
||||
$MAKE -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/commandfilter \
|
||||
--single unit/moduleapi/basics \
|
||||
--single unit/moduleapi/fork \
|
||||
--single unit/moduleapi/testrdb \
|
||||
--single unit/moduleapi/infotest \
|
||||
@@ -35,4 +36,5 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/hash \
|
||||
--single unit/moduleapi/zset \
|
||||
--single unit/moduleapi/stream \
|
||||
--single unit/moduleapi/cluster \
|
||||
"${@}"
|
||||
|
||||
+40
-18
@@ -21,7 +21,12 @@ NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
STD=-pedantic -DREDIS_STATIC=''
|
||||
|
||||
# Use -Wno-c11-extensions on clang, either where explicitly used or on
|
||||
# platforms we can assume it's being used.
|
||||
ifneq (,$(findstring clang,$(CC)))
|
||||
STD+=-Wno-c11-extensions
|
||||
else
|
||||
ifneq (,$(findstring FreeBSD,$(uname_S)))
|
||||
STD+=-Wno-c11-extensions
|
||||
endif
|
||||
@@ -29,8 +34,10 @@ endif
|
||||
WARN=-Wall -W -Wno-missing-field-initializers
|
||||
OPT=$(OPTIMIZATION)
|
||||
|
||||
# Detect if the compiler supports C11 _Atomic
|
||||
C11_ATOMIC := $(shell sh -c 'echo "\#include <stdatomic.h>" > foo.c; \
|
||||
# Detect if the compiler supports C11 _Atomic.
|
||||
# NUMBER_SIGN_CHAR is a workaround to support both GNU Make 4.3 and older versions.
|
||||
NUMBER_SIGN_CHAR := \#
|
||||
C11_ATOMIC := $(shell sh -c 'echo "$(NUMBER_SIGN_CHAR)include <stdatomic.h>" > foo.c; \
|
||||
$(CC) -std=c11 -c foo.c -o foo.o > /dev/null 2>&1; \
|
||||
if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c')
|
||||
ifeq ($(C11_ATOMIC),yes)
|
||||
@@ -88,14 +95,10 @@ FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
# Linux ARM needs -latomic at linking time
|
||||
ifneq (,$(filter aarch64 armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
else
|
||||
# Linux ARM32 needs -latomic at linking time
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
@@ -120,12 +123,10 @@ ifeq ($(uname_S),Darwin)
|
||||
# must be referenced explicitly during build.
|
||||
ifeq ($(uname_M),arm64)
|
||||
# Homebrew arm64 uses /opt/homebrew as HOMEBREW_PREFIX
|
||||
OPENSSL_CFLAGS=-I/opt/homebrew/opt/openssl/include
|
||||
OPENSSL_LDFLAGS=-L/opt/homebrew/opt/openssl/lib
|
||||
OPENSSL_PREFIX?=/opt/homebrew/opt/openssl
|
||||
else
|
||||
# Homebrew x86/ppc uses /usr/local as HOMEBREW_PREFIX
|
||||
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
||||
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||
OPENSSL_PREFIX?=/usr/local/opt/openssl
|
||||
endif
|
||||
else
|
||||
ifeq ($(uname_S),AIX)
|
||||
@@ -187,6 +188,14 @@ endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef OPENSSL_PREFIX
|
||||
OPENSSL_CFLAGS=-I$(OPENSSL_PREFIX)/include
|
||||
OPENSSL_LDFLAGS=-L$(OPENSSL_PREFIX)/lib
|
||||
# Also export OPENSSL_PREFIX so it ends up in deps sub-Makefiles
|
||||
export OPENSSL_PREFIX
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
|
||||
|
||||
@@ -251,6 +260,17 @@ endif
|
||||
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS)
|
||||
endif
|
||||
|
||||
ifndef V
|
||||
define MAKE_INSTALL
|
||||
@printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$(1)$(ENDCOLOR) 1>&2
|
||||
@$(INSTALL) $(1) $(2)
|
||||
endef
|
||||
else
|
||||
define MAKE_INSTALL
|
||||
$(INSTALL) $(1) $(2)
|
||||
endef
|
||||
endif
|
||||
|
||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
|
||||
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
|
||||
@@ -346,9 +366,6 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
|
||||
|
||||
dict-benchmark: dict.c zmalloc.c sds.c siphash.c mt19937-64.c
|
||||
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
||||
|
||||
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
||||
-include $(DEP)
|
||||
|
||||
@@ -359,13 +376,15 @@ DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ
|
||||
$(REDIS_CC) -MMD -o $@ -c $<
|
||||
|
||||
clean:
|
||||
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
|
||||
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep
|
||||
rm -f $(DEP)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
distclean: clean
|
||||
-(cd ../deps && $(MAKE) distclean)
|
||||
-(cd modules && $(MAKE) clean)
|
||||
-(cd ../tests/modules && $(MAKE) clean)
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
@@ -373,6 +392,9 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
test-modules: $(REDIS_SERVER_NAME)
|
||||
@(cd ..; ./runtest-moduleapi)
|
||||
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME)
|
||||
@(cd ..; ./runtest-sentinel)
|
||||
|
||||
@@ -412,9 +434,9 @@ src/help.h:
|
||||
|
||||
install: all
|
||||
@mkdir -p $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
||||
$(call MAKE_INSTALL,$(REDIS_SERVER_NAME),$(INSTALL_BIN))
|
||||
$(call MAKE_INSTALL,$(REDIS_BENCHMARK_NAME),$(INSTALL_BIN))
|
||||
$(call MAKE_INSTALL,$(REDIS_CLI_NAME),$(INSTALL_BIN))
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||
|
||||
@@ -245,7 +245,7 @@ user *ACLCreateUser(const char *name, size_t namelen) {
|
||||
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
||||
user *u = zmalloc(sizeof(*u));
|
||||
u->name = sdsnewlen(name,namelen);
|
||||
u->flags = USER_FLAG_DISABLED | server.acl_pubusub_default;
|
||||
u->flags = USER_FLAG_DISABLED | server.acl_pubsub_default;
|
||||
u->allowed_subcommands = NULL;
|
||||
u->passwords = listCreate();
|
||||
u->patterns = listCreate();
|
||||
@@ -652,6 +652,7 @@ sds ACLDescribeUser(user *u) {
|
||||
if (u->flags & USER_FLAG_ALLCHANNELS) {
|
||||
res = sdscatlen(res,"&* ",3);
|
||||
} else {
|
||||
res = sdscatlen(res,"resetchannels ",14);
|
||||
listRewind(u->channels,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispat = listNodeValue(ln);
|
||||
@@ -1000,6 +1001,8 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
|
||||
serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
|
||||
serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK);
|
||||
if (server.acl_pubsub_default & USER_FLAG_ALLCHANNELS)
|
||||
serverAssert(ACLSetUser(u,"allchannels",-1) == C_OK);
|
||||
serverAssert(ACLSetUser(u,"off",-1) == C_OK);
|
||||
serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK);
|
||||
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
|
||||
@@ -1054,7 +1057,6 @@ void ACLInit(void) {
|
||||
UsersToLoad = listCreate();
|
||||
ACLLog = listCreate();
|
||||
ACLInitDefaultUser();
|
||||
server.requirepass = NULL; /* Only used for backward compatibility. */
|
||||
}
|
||||
|
||||
/* Check the username and password pair and return C_OK if they are valid,
|
||||
@@ -1181,9 +1183,9 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
||||
/* If there is no associated user, the connection can run anything. */
|
||||
if (u == NULL) return ACL_OK;
|
||||
|
||||
/* Check if the user can execute this command. */
|
||||
if (!(u->flags & USER_FLAG_ALLCOMMANDS) &&
|
||||
c->cmd->proc != authCommand)
|
||||
/* Check if the user can execute this command or if the command
|
||||
* doesn't need to be authenticated (hello, auth). */
|
||||
if (!(u->flags & USER_FLAG_ALLCOMMANDS) && !(c->cmd->flags & CMD_NO_AUTH))
|
||||
{
|
||||
/* If the bit is not set we have to check further, in case the
|
||||
* command is allowed just with that specific subcommand. */
|
||||
@@ -1282,7 +1284,14 @@ void ACLKillPubsubClientsIfNeeded(user *u, list *upcoming) {
|
||||
listNode *ln, *lpn;
|
||||
robj *o;
|
||||
int kill = 0;
|
||||
|
||||
|
||||
/* If any of the original rule has the all-channels permission, but the new
|
||||
* one doesn't (verifed by the caller), then the new list is not a strict
|
||||
* superset of the original, and the next loop can be skipped. */
|
||||
if (u->flags & USER_FLAG_ALLCHANNELS) {
|
||||
kill = 1;
|
||||
}
|
||||
|
||||
/* Nothing to kill when the upcoming are a literal super set of the original
|
||||
* permissions. */
|
||||
listRewind(u->channels,&li);
|
||||
@@ -1361,6 +1370,22 @@ int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr)
|
||||
|
||||
}
|
||||
|
||||
/* Check whether the command is ready to be exceuted by ACLCheckCommandPerm.
|
||||
* If check passes, then check whether pub/sub channels of the command is
|
||||
* ready to be executed by ACLCheckPubsubPerm */
|
||||
int ACLCheckAllPerm(client *c, int *idxptr) {
|
||||
int acl_retval = ACLCheckCommandPerm(c,idxptr);
|
||||
if (acl_retval != ACL_OK)
|
||||
return acl_retval;
|
||||
if (c->cmd->proc == publishCommand)
|
||||
acl_retval = ACLCheckPubsubPerm(c,1,1,0,idxptr);
|
||||
else if (c->cmd->proc == subscribeCommand)
|
||||
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,0,idxptr);
|
||||
else if (c->cmd->proc == psubscribeCommand)
|
||||
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,1,idxptr);
|
||||
return acl_retval;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL loading / saving functions
|
||||
* ==========================================================================*/
|
||||
@@ -1874,6 +1899,12 @@ void addACLLogEntry(client *c, int reason, int argpos, sds username) {
|
||||
void aclCommand(client *c) {
|
||||
char *sub = c->argv[1]->ptr;
|
||||
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
|
||||
/* Initially redact all of the arguments to not leak any information
|
||||
* about the user. */
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
redactClientCommandArgument(c, j);
|
||||
}
|
||||
|
||||
sds username = c->argv[2]->ptr;
|
||||
/* Check username validity. */
|
||||
if (ACLStringHasSpaces(username,sdslen(username))) {
|
||||
@@ -2223,6 +2254,8 @@ void authCommand(client *c) {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
/* Always redact the second argument */
|
||||
redactClientCommandArgument(c, 1);
|
||||
|
||||
/* Handle the two different forms here. The form with two arguments
|
||||
* will just use "default" as username. */
|
||||
@@ -2242,6 +2275,7 @@ void authCommand(client *c) {
|
||||
} else {
|
||||
username = c->argv[1];
|
||||
password = c->argv[2];
|
||||
redactClientCommandArgument(c, 2);
|
||||
}
|
||||
|
||||
if (ACLAuthenticateUser(c,username,password) == C_OK) {
|
||||
@@ -2251,3 +2285,15 @@ void authCommand(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the password for the "default" ACL user. This implements supports for
|
||||
* requirepass config, so passing in NULL will set the user to be nopass. */
|
||||
void ACLUpdateDefaultUserPassword(sds password) {
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
if (password) {
|
||||
sds aclop = sdscatlen(sdsnew(">"), password, sdslen(password));
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
} else {
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
|
||||
return AE_ERR; /* NO event with the specified ID found */
|
||||
}
|
||||
|
||||
/* How many milliseconds until the first timer should fire.
|
||||
/* How many microseconds until the first timer should fire.
|
||||
* If there are no timers, -1 is returned.
|
||||
*
|
||||
* Note that's O(N) since time events are unsorted.
|
||||
@@ -248,7 +248,7 @@ int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
|
||||
* Much better but still insertion or deletion of timers is O(N).
|
||||
* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
|
||||
*/
|
||||
static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
|
||||
static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) {
|
||||
aeTimeEvent *te = eventLoop->timeEventHead;
|
||||
if (te == NULL) return -1;
|
||||
|
||||
@@ -260,8 +260,7 @@ static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
|
||||
}
|
||||
|
||||
monotime now = getMonotonicUs();
|
||||
return (now >= earliest->when)
|
||||
? 0 : (long)((earliest->when - now) / 1000);
|
||||
return (now >= earliest->when) ? 0 : earliest->when - now;
|
||||
}
|
||||
|
||||
/* Process time events */
|
||||
@@ -361,14 +360,14 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
|
||||
int j;
|
||||
struct timeval tv, *tvp;
|
||||
long msUntilTimer = -1;
|
||||
int64_t usUntilTimer = -1;
|
||||
|
||||
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
|
||||
msUntilTimer = msUntilEarliestTimer(eventLoop);
|
||||
usUntilTimer = usUntilEarliestTimer(eventLoop);
|
||||
|
||||
if (msUntilTimer >= 0) {
|
||||
tv.tv_sec = msUntilTimer / 1000;
|
||||
tv.tv_usec = (msUntilTimer % 1000) * 1000;
|
||||
if (usUntilTimer >= 0) {
|
||||
tv.tv_sec = usUntilTimer / 1000000;
|
||||
tv.tv_usec = usUntilTimer % 1000000;
|
||||
tvp = &tv;
|
||||
} else {
|
||||
/* If we have to check for events but need to return
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
int retval, numevents = 0;
|
||||
|
||||
retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
|
||||
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
|
||||
tvp ? (tvp->tv_sec*1000 + (tvp->tv_usec + 999)/1000) : -1);
|
||||
if (retval > 0) {
|
||||
int j;
|
||||
|
||||
|
||||
+55
-7
@@ -36,8 +36,29 @@
|
||||
typedef struct aeApiState {
|
||||
int kqfd;
|
||||
struct kevent *events;
|
||||
|
||||
/* Events mask for merge read and write event.
|
||||
* To reduce memory consumption, we use 2 bits to store the mask
|
||||
* of an event, so that 1 byte will store the mask of 4 events. */
|
||||
char *eventsMask;
|
||||
} aeApiState;
|
||||
|
||||
#define EVENT_MASK_MALLOC_SIZE(sz) (((sz) + 3) / 4)
|
||||
#define EVENT_MASK_OFFSET(fd) ((fd) % 4 * 2)
|
||||
#define EVENT_MASK_ENCODE(fd, mask) (((mask) & 0x3) << EVENT_MASK_OFFSET(fd))
|
||||
|
||||
static inline int getEventMask(const char *eventsMask, int fd) {
|
||||
return (eventsMask[fd/4] >> EVENT_MASK_OFFSET(fd)) & 0x3;
|
||||
}
|
||||
|
||||
static inline void addEventMask(char *eventsMask, int fd, int mask) {
|
||||
eventsMask[fd/4] |= EVENT_MASK_ENCODE(fd, mask);
|
||||
}
|
||||
|
||||
static inline void resetEventMask(char *eventsMask, int fd) {
|
||||
eventsMask[fd/4] &= ~EVENT_MASK_ENCODE(fd, 0x3);
|
||||
}
|
||||
|
||||
static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
aeApiState *state = zmalloc(sizeof(aeApiState));
|
||||
|
||||
@@ -54,6 +75,8 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
return -1;
|
||||
}
|
||||
anetCloexec(state->kqfd);
|
||||
state->eventsMask = zmalloc(EVENT_MASK_MALLOC_SIZE(eventLoop->setsize));
|
||||
memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(eventLoop->setsize));
|
||||
eventLoop->apidata = state;
|
||||
return 0;
|
||||
}
|
||||
@@ -62,6 +85,8 @@ static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
state->events = zrealloc(state->events, sizeof(struct kevent)*setsize);
|
||||
state->eventsMask = zrealloc(state->eventsMask, EVENT_MASK_MALLOC_SIZE(setsize));
|
||||
memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(setsize));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -70,6 +95,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
|
||||
close(state->kqfd);
|
||||
zfree(state->events);
|
||||
zfree(state->eventsMask);
|
||||
zfree(state);
|
||||
}
|
||||
|
||||
@@ -120,15 +146,37 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
if (retval > 0) {
|
||||
int j;
|
||||
|
||||
numevents = retval;
|
||||
for(j = 0; j < numevents; j++) {
|
||||
int mask = 0;
|
||||
/* Normally we execute the read event first and then the write event.
|
||||
* When the barrier is set, we will do it reverse.
|
||||
*
|
||||
* However, under kqueue, read and write events would be separate
|
||||
* events, which would make it impossible to control the order of
|
||||
* reads and writes. So we store the event's mask we've got and merge
|
||||
* the same fd events later. */
|
||||
for (j = 0; j < retval; j++) {
|
||||
struct kevent *e = state->events+j;
|
||||
int fd = e->ident;
|
||||
int mask = 0;
|
||||
|
||||
if (e->filter == EVFILT_READ) mask |= AE_READABLE;
|
||||
if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE;
|
||||
eventLoop->fired[j].fd = e->ident;
|
||||
eventLoop->fired[j].mask = mask;
|
||||
if (e->filter == EVFILT_READ) mask = AE_READABLE;
|
||||
else if (e->filter == EVFILT_WRITE) mask = AE_WRITABLE;
|
||||
addEventMask(state->eventsMask, fd, mask);
|
||||
}
|
||||
|
||||
/* Re-traversal to merge read and write events, and set the fd's mask to
|
||||
* 0 so that events are not added again when the fd is encountered again. */
|
||||
numevents = 0;
|
||||
for (j = 0; j < retval; j++) {
|
||||
struct kevent *e = state->events+j;
|
||||
int fd = e->ident;
|
||||
int mask = getEventMask(state->eventsMask, fd);
|
||||
|
||||
if (mask) {
|
||||
eventLoop->fired[numevents].fd = fd;
|
||||
eventLoop->fired[numevents].mask = mask;
|
||||
resetEventMask(state->eventsMask, fd);
|
||||
numevents++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return numevents;
|
||||
|
||||
+6
-78
@@ -186,27 +186,6 @@ int anetDisableTcpNoDelay(char *err, int fd)
|
||||
return anetSetTcpNoDelay(err, fd, 0);
|
||||
}
|
||||
|
||||
|
||||
int anetSetSendBuffer(char *err, int fd, int buffsize)
|
||||
{
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
|
||||
{
|
||||
anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetTcpKeepAlive(char *err, int fd)
|
||||
{
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* Set the socket send timeout (SO_SNDTIMEO socket option) to the specified
|
||||
* number of milliseconds, or disable it if the 'ms' argument is zero. */
|
||||
int anetSendTimeout(char *err, int fd, long long ms) {
|
||||
@@ -378,23 +357,11 @@ end:
|
||||
}
|
||||
}
|
||||
|
||||
int anetTcpConnect(char *err, const char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockConnect(char *err, const char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr)
|
||||
{
|
||||
@@ -430,53 +397,16 @@ int anetUnixGenericConnect(char *err, const char *path, int flags)
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetUnixConnect(char *err, const char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetUnixNonBlockConnect(char *err, const char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
/* Like read(2) but make sure 'count' is read before to return
|
||||
* (unless error or EOF condition is encountered) */
|
||||
int anetRead(int fd, char *buf, int count)
|
||||
{
|
||||
ssize_t nread, totlen = 0;
|
||||
while(totlen != count) {
|
||||
nread = read(fd,buf,count-totlen);
|
||||
if (nread == 0) return totlen;
|
||||
if (nread == -1) return -1;
|
||||
totlen += nread;
|
||||
buf += nread;
|
||||
}
|
||||
return totlen;
|
||||
}
|
||||
|
||||
/* Like write(2) but make sure 'count' is written before to return
|
||||
* (unless error is encountered) */
|
||||
int anetWrite(int fd, char *buf, int count)
|
||||
{
|
||||
ssize_t nwritten, totlen = 0;
|
||||
while(totlen != count) {
|
||||
nwritten = write(fd,buf,count-totlen);
|
||||
if (nwritten == 0) return totlen;
|
||||
if (nwritten == -1) return -1;
|
||||
totlen += nwritten;
|
||||
buf += nwritten;
|
||||
}
|
||||
return totlen;
|
||||
}
|
||||
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog, mode_t perm) {
|
||||
if (bind(s,sa,len) == -1) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
if (sa->sa_family == AF_LOCAL && perm)
|
||||
chmod(((struct sockaddr_un *) sa)->sun_path, perm);
|
||||
|
||||
if (listen(s, backlog) == -1) {
|
||||
anetSetError(err, "listen: %s", strerror(errno));
|
||||
close(s);
|
||||
@@ -520,7 +450,7 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
||||
|
||||
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog,0) == ANET_ERR) s = ANET_ERR;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
@@ -557,10 +487,8 @@ int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog) == ANET_ERR)
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog,perm) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
if (perm)
|
||||
chmod(sa.sun_path, perm);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,26 +53,19 @@
|
||||
#define FD_TO_PEER_NAME 0
|
||||
#define FD_TO_SOCK_NAME 1
|
||||
|
||||
int anetTcpConnect(char *err, const char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||
int anetUnixConnect(char *err, const char *path);
|
||||
int anetUnixNonBlockConnect(char *err, const char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
int anetNonBlock(char *err, int fd);
|
||||
int anetBlock(char *err, int fd);
|
||||
int anetCloexec(int fd);
|
||||
int anetEnableTcpNoDelay(char *err, int fd);
|
||||
int anetDisableTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetSendTimeout(char *err, int fd, long long ms);
|
||||
int anetRecvTimeout(char *err, int fd, long long ms);
|
||||
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type);
|
||||
|
||||
@@ -162,7 +162,9 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
|
||||
/* Install a file event to send data to the rewrite child if there is
|
||||
* not one already. */
|
||||
if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
|
||||
if (!server.aof_stop_sending_diff &&
|
||||
aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0)
|
||||
{
|
||||
aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
|
||||
AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
}
|
||||
@@ -218,7 +220,7 @@ void killAppendOnlyChild(void) {
|
||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||
(long) server.child_pid);
|
||||
if (kill(server.child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != server.child_pid);
|
||||
while(waitpid(-1, &statloc, 0) != server.child_pid);
|
||||
}
|
||||
/* Reset the buffer accumulating changes while the child saves. */
|
||||
aofRewriteBufferReset();
|
||||
@@ -234,9 +236,12 @@ void killAppendOnlyChild(void) {
|
||||
void stopAppendOnly(void) {
|
||||
serverAssert(server.aof_state != AOF_OFF);
|
||||
flushAppendOnlyFile(1);
|
||||
redis_fsync(server.aof_fd);
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
if (redis_fsync(server.aof_fd) == -1) {
|
||||
serverLog(LL_WARNING,"Fail to fsync the AOF file: %s",strerror(errno));
|
||||
} else {
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
}
|
||||
close(server.aof_fd);
|
||||
|
||||
server.aof_fd = -1;
|
||||
@@ -290,6 +295,15 @@ int startAppendOnly(void) {
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
server.aof_fd = newfd;
|
||||
|
||||
/* If AOF fsync error in bio job, we just ignore it and log the event. */
|
||||
int aof_bio_fsync_status;
|
||||
atomicGet(server.aof_bio_fsync_status, aof_bio_fsync_status);
|
||||
if (aof_bio_fsync_status == C_ERR) {
|
||||
serverLog(LL_WARNING,
|
||||
"AOF reopen, just ignore the AOF fsync error in bio job");
|
||||
atomicSet(server.aof_bio_fsync_status,C_OK);
|
||||
}
|
||||
|
||||
/* If AOF was in error state, we just ignore it and log the event. */
|
||||
if (server.aof_last_write_status == C_ERR) {
|
||||
serverLog(LL_WARNING,"AOF reopen, just ignore the last error.");
|
||||
@@ -1590,7 +1604,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;
|
||||
if (anetNonBlock(NULL,server.aof_pipe_read_ack_from_parent) != ANET_OK)
|
||||
goto werr;
|
||||
/* We read the ACK from the server using a 10 seconds timeout. Normally
|
||||
/* We read the ACK from the server using a 5 seconds timeout. Normally
|
||||
* it should reply ASAP, but just in case we lose its reply, we are sure
|
||||
* the child will eventually get terminated. */
|
||||
if (syncRead(server.aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||
|
||||
|
||||
+2
-2
@@ -31,13 +31,13 @@ const char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
|
||||
" .-`` .-```. ```\\/ _.,_ ''-._ \n"
|
||||
" .-`` .-```. ```\\/ _.,_ ''-._ \n"
|
||||
" ( ' , .-` | `, ) Running in %s mode\n"
|
||||
" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n"
|
||||
" | `-._ `._ / _.-' | PID: %ld\n"
|
||||
" `-._ `-._ `-./ _.-' _.-' \n"
|
||||
" |`-._`-._ `-.__.-' _.-'_.-'| \n"
|
||||
" | `-._`-._ _.-'_.-' | http://redis.io \n"
|
||||
" | `-._`-._ _.-'_.-' | https://redis.io \n"
|
||||
" `-._ `-._`-.__.-'_.-' _.-' \n"
|
||||
" |`-._`-._ `-.__.-' _.-'_.-'| \n"
|
||||
" | `-._`-._ _.-'_.-' | \n"
|
||||
|
||||
@@ -220,7 +220,23 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
if (type == BIO_CLOSE_FILE) {
|
||||
close(job->fd);
|
||||
} else if (type == BIO_AOF_FSYNC) {
|
||||
redis_fsync(job->fd);
|
||||
/* The fd may be closed by main thread and reused for another
|
||||
* socket, pipe, or file. We just ignore these errno because
|
||||
* aof fsync did not really fail. */
|
||||
if (redis_fsync(job->fd) == -1 &&
|
||||
errno != EBADF && errno != EINVAL)
|
||||
{
|
||||
int last_status;
|
||||
atomicGet(server.aof_bio_fsync_status,last_status);
|
||||
atomicSet(server.aof_bio_fsync_status,C_ERR);
|
||||
atomicSet(server.aof_bio_fsync_errno,errno);
|
||||
if (last_status == C_OK) {
|
||||
serverLog(LL_WARNING,
|
||||
"Fail to fsync the AOF file: %s",strerror(errno));
|
||||
}
|
||||
} else {
|
||||
atomicSet(server.aof_bio_fsync_status,C_OK);
|
||||
}
|
||||
} else if (type == BIO_LAZY_FREE) {
|
||||
job->free_fn(job->free_args);
|
||||
} else {
|
||||
|
||||
+19
-18
@@ -37,8 +37,8 @@
|
||||
/* Count number of bits set in the binary array pointed by 's' and long
|
||||
* 'count' bytes. The implementation of this function is required to
|
||||
* work with an input string length up to 512 MB or more (server.proto_max_bulk_len) */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t bits = 0;
|
||||
long long redisPopcount(void *s, long count) {
|
||||
long long bits = 0;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
|
||||
@@ -98,11 +98,11 @@ size_t redisPopcount(void *s, long count) {
|
||||
* no zero bit is found, it returns count*8 assuming the string is zero
|
||||
* padded on the right. However if 'bit' is 1 it is possible that there is
|
||||
* not a single set bit in the bitmap. In this special case -1 is returned. */
|
||||
long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
long long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
long long pos = 0; /* Position of bit, to return to the caller. */
|
||||
unsigned long j;
|
||||
int found;
|
||||
|
||||
@@ -328,8 +328,9 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
|
||||
|
||||
/* Note that maxincr and minincr could overflow, but we use the values
|
||||
* only after checking 'value' range, so when we use it no overflow
|
||||
* happens. */
|
||||
int64_t maxincr = max-value;
|
||||
* happens. 'uint64_t' cast is there just to prevent undefined behavior on
|
||||
* overflow */
|
||||
int64_t maxincr = (uint64_t)max-value;
|
||||
int64_t minincr = min-value;
|
||||
|
||||
if (value > max || (bits != 64 && incr > maxincr) || (value >= 0 && incr > 0 && incr > maxincr))
|
||||
@@ -410,7 +411,7 @@ void printBits(unsigned char *p, unsigned long count) {
|
||||
* If the 'hash' argument is true, and 'bits is positive, then the command
|
||||
* will also parse bit offsets prefixed by "#". In such a case the offset
|
||||
* is multiplied by 'bits'. This is useful for the BITFIELD command. */
|
||||
int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int bits) {
|
||||
int getBitOffsetFromArgument(client *c, robj *o, uint64_t *offset, int hash, int bits) {
|
||||
long long loffset;
|
||||
char *err = "bit offset is not an integer or out of range";
|
||||
char *p = o->ptr;
|
||||
@@ -435,7 +436,7 @@ int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int b
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
*offset = (size_t)loffset;
|
||||
*offset = loffset;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -477,7 +478,7 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
|
||||
* so that the 'maxbit' bit can be addressed. The object is finally
|
||||
* returned. Otherwise if the key holds a wrong type NULL is returned and
|
||||
* an error is sent to the client. */
|
||||
robj *lookupStringForBitCommand(client *c, size_t maxbit) {
|
||||
robj *lookupStringForBitCommand(client *c, uint64_t maxbit) {
|
||||
size_t byte = maxbit >> 3;
|
||||
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (checkType(c,o,OBJ_STRING)) return NULL;
|
||||
@@ -527,7 +528,7 @@ unsigned char *getObjectReadOnlyString(robj *o, long *len, char *llbuf) {
|
||||
void setbitCommand(client *c) {
|
||||
robj *o;
|
||||
char *err = "bit is not an integer or out of range";
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
ssize_t byte, bit;
|
||||
int byteval, bitval;
|
||||
long on;
|
||||
@@ -566,7 +567,7 @@ void setbitCommand(client *c) {
|
||||
void getbitCommand(client *c) {
|
||||
robj *o;
|
||||
char llbuf[32];
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
size_t byte, bit;
|
||||
size_t bitval = 0;
|
||||
|
||||
@@ -888,7 +889,7 @@ void bitposCommand(client *c) {
|
||||
addReplyLongLong(c, -1);
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
long pos = redisBitpos(p+start,bytes,bit);
|
||||
long long pos = redisBitpos(p+start,bytes,bit);
|
||||
|
||||
/* If we are looking for clear bits, and the user specified an exact
|
||||
* range with start-end, we can't consider the right of the range as
|
||||
@@ -897,11 +898,11 @@ void bitposCommand(client *c) {
|
||||
* So if redisBitpos() returns the first bit outside the range,
|
||||
* we return -1 to the caller, to mean, in the specified range there
|
||||
* is not a single "0" bit. */
|
||||
if (end_given && bit == 0 && pos == bytes*8) {
|
||||
if (end_given && bit == 0 && pos == (long long)bytes<<3) {
|
||||
addReplyLongLong(c,-1);
|
||||
return;
|
||||
}
|
||||
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
|
||||
if (pos != -1) pos += (long long)start<<3; /* Adjust for the bytes we skipped. */
|
||||
addReplyLongLong(c,pos);
|
||||
}
|
||||
}
|
||||
@@ -933,12 +934,12 @@ struct bitfieldOp {
|
||||
* GET subcommand is allowed, other subcommands will return an error. */
|
||||
void bitfieldGeneric(client *c, int flags) {
|
||||
robj *o;
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
|
||||
int readonly = 1;
|
||||
size_t highest_write_offset = 0;
|
||||
uint64_t highest_write_offset = 0;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
int remargs = c->argc-j-1; /* Remaining args other than current. */
|
||||
@@ -1128,9 +1129,9 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
* object boundaries. */
|
||||
memset(buf,0,9);
|
||||
int i;
|
||||
size_t byte = thisop->offset >> 3;
|
||||
uint64_t byte = thisop->offset >> 3;
|
||||
for (i = 0; i < 9; i++) {
|
||||
if (src == NULL || i+byte >= (size_t)strlen) break;
|
||||
if (src == NULL || i+byte >= (uint64_t)strlen) break;
|
||||
buf[i] = src[i+byte];
|
||||
}
|
||||
|
||||
|
||||
+42
-8
@@ -87,6 +87,11 @@ typedef struct bkinfo {
|
||||
* flag is set client query buffer is not longer processed, but accumulated,
|
||||
* and will be processed when the client is unblocked. */
|
||||
void blockClient(client *c, int btype) {
|
||||
/* Master client should never be blocked unless pause or module */
|
||||
serverAssert(!(c->flags & CLIENT_MASTER &&
|
||||
btype != BLOCKED_MODULE &&
|
||||
btype != BLOCKED_PAUSE));
|
||||
|
||||
c->flags |= CLIENT_BLOCKED;
|
||||
c->btype = btype;
|
||||
server.blocked_clients++;
|
||||
@@ -106,12 +111,11 @@ void blockClient(client *c, int btype) {
|
||||
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){
|
||||
const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us;
|
||||
c->lastcmd->microseconds += total_cmd_duration;
|
||||
|
||||
/* Log the command into the Slow log if needed. */
|
||||
if (!(c->lastcmd->flags & CMD_SKIP_SLOWLOG)) {
|
||||
slowlogPushEntryIfNeeded(c,c->argv,c->argc,total_cmd_duration);
|
||||
/* Log the reply duration event. */
|
||||
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
|
||||
}
|
||||
slowlogPushCurrentCommand(c, c->lastcmd, total_cmd_duration);
|
||||
/* Log the reply duration event. */
|
||||
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
@@ -188,6 +192,16 @@ void unblockClient(client *c) {
|
||||
} else {
|
||||
serverPanic("Unknown btype in unblockClient().");
|
||||
}
|
||||
|
||||
/* Reset the client for a new query since, for blocking commands
|
||||
* we do not do it immediately after the command returns (when the
|
||||
* client got blocked) in order to be still able to access the argument
|
||||
* vector from module callbacks and updateStatsOnUnblock. */
|
||||
if (c->btype != BLOCKED_PAUSE) {
|
||||
freeClientOriginalArgv(c);
|
||||
resetClient(c);
|
||||
}
|
||||
|
||||
/* Clear the flags, and put the client in the unblocked list so that
|
||||
* we'll process new commands in its query buffer ASAP. */
|
||||
server.blocked_clients--;
|
||||
@@ -279,8 +293,9 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
* freed by the next unblockClient()
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
unblockClient(receiver);
|
||||
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
@@ -292,6 +307,9 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
listTypePush(o,value,wherefrom);
|
||||
}
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
unblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
decrRefCount(value);
|
||||
@@ -335,11 +353,15 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
unblockClient(receiver);
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
unblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
zcard--;
|
||||
|
||||
/* Replicate the command. */
|
||||
@@ -429,6 +451,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
}
|
||||
}
|
||||
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
/* Emit the two elements sub-array consisting of
|
||||
@@ -457,6 +481,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,6 +497,10 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
dictEntry *de;
|
||||
|
||||
/* Optimization: If no clients are in type BLOCKED_MODULE,
|
||||
* we can skip this loop. */
|
||||
if (!server.blocked_clients_by_type[BLOCKED_MODULE]) return;
|
||||
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
@@ -497,12 +527,16 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
* different modules with different triggers to consider if a key
|
||||
* is ready or not. This means we can't exit the loop but need
|
||||
* to continue after the first failure. */
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
|
||||
moduleUnblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,7 +587,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
* way we can lookup an object multiple times (BLMOVE does
|
||||
* that) without the risk of it being freed in the second
|
||||
* lookup, invalidating the first one.
|
||||
* See https://github.com/antirez/redis/pull/6554. */
|
||||
* See https://github.com/redis/redis/pull/6554. */
|
||||
server.fixed_time_expire++;
|
||||
updateCachedTime(0);
|
||||
|
||||
|
||||
+40
-13
@@ -33,6 +33,7 @@
|
||||
typedef struct {
|
||||
size_t keys;
|
||||
size_t cow;
|
||||
monotime cow_updated;
|
||||
double progress;
|
||||
childInfoType information_type; /* Type of information */
|
||||
} child_info_data;
|
||||
@@ -69,29 +70,53 @@ void closeChildInfoPipe(void) {
|
||||
void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, char *pname) {
|
||||
if (server.child_info_pipe[1] == -1) return;
|
||||
|
||||
child_info_data data = {0}; /* zero everything, including padding to sattisfy valgrind */
|
||||
static monotime cow_updated = 0;
|
||||
static uint64_t cow_update_cost = 0;
|
||||
static size_t cow = 0;
|
||||
|
||||
child_info_data data = {0}; /* zero everything, including padding to satisfy valgrind */
|
||||
|
||||
/* When called to report current info, we need to throttle down CoW updates as they
|
||||
* can be very expensive. To do that, we measure the time it takes to get a reading
|
||||
* and schedule the next reading to happen not before time*CHILD_COW_COST_FACTOR
|
||||
* passes. */
|
||||
|
||||
monotime now = getMonotonicUs();
|
||||
if (info_type != CHILD_INFO_TYPE_CURRENT_INFO ||
|
||||
!cow_updated ||
|
||||
now - cow_updated > cow_update_cost * CHILD_COW_DUTY_CYCLE)
|
||||
{
|
||||
cow = zmalloc_get_private_dirty(-1);
|
||||
cow_updated = getMonotonicUs();
|
||||
cow_update_cost = cow_updated - now;
|
||||
|
||||
if (cow) {
|
||||
serverLog((info_type == CHILD_INFO_TYPE_CURRENT_INFO) ? LL_VERBOSE : LL_NOTICE,
|
||||
"%s: %zu MB of memory used by copy-on-write",
|
||||
pname, cow / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
data.information_type = info_type;
|
||||
data.keys = keys;
|
||||
data.cow = zmalloc_get_private_dirty(-1);
|
||||
data.cow = cow;
|
||||
data.cow_updated = cow_updated;
|
||||
data.progress = progress;
|
||||
|
||||
if (data.cow) {
|
||||
serverLog((info_type == CHILD_INFO_TYPE_CURRENT_INFO) ? LL_VERBOSE : LL_NOTICE,
|
||||
"%s: %zu MB of memory used by copy-on-write",
|
||||
pname, data.cow/(1024*1024));
|
||||
}
|
||||
|
||||
ssize_t wlen = sizeof(data);
|
||||
|
||||
if (write(server.child_info_pipe[1], &data, wlen) != wlen) {
|
||||
/* Nothing to do on error, this will be detected by the other side. */
|
||||
/* Failed writing to parent, it could have been killed, exit. */
|
||||
serverLog(LL_WARNING,"Child failed reporting info to parent, exiting. %s", strerror(errno));
|
||||
exitFromChild(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update Child info. */
|
||||
void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, double progress) {
|
||||
void updateChildInfo(childInfoType information_type, size_t cow, monotime cow_updated, size_t keys, double progress) {
|
||||
if (information_type == CHILD_INFO_TYPE_CURRENT_INFO) {
|
||||
server.stat_current_cow_bytes = cow;
|
||||
server.stat_current_cow_updated = cow_updated;
|
||||
server.stat_current_save_keys_processed = keys;
|
||||
if (progress != -1) server.stat_module_progress = progress;
|
||||
} else if (information_type == CHILD_INFO_TYPE_AOF_COW_SIZE) {
|
||||
@@ -107,7 +132,7 @@ void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, do
|
||||
* if complete data read into the buffer,
|
||||
* data is stored into *buffer, and returns 1.
|
||||
* otherwise, the partial data is left in the buffer, waiting for the next read, and returns 0. */
|
||||
int readChildInfo(childInfoType *information_type, size_t *cow, size_t *keys, double* progress) {
|
||||
int readChildInfo(childInfoType *information_type, size_t *cow, monotime *cow_updated, size_t *keys, double* progress) {
|
||||
/* We are using here a static buffer in combination with the server.child_info_nread to handle short reads */
|
||||
static child_info_data buffer;
|
||||
ssize_t wlen = sizeof(buffer);
|
||||
@@ -124,6 +149,7 @@ int readChildInfo(childInfoType *information_type, size_t *cow, size_t *keys, do
|
||||
if (server.child_info_nread == wlen) {
|
||||
*information_type = buffer.information_type;
|
||||
*cow = buffer.cow;
|
||||
*cow_updated = buffer.cow_updated;
|
||||
*keys = buffer.keys;
|
||||
*progress = buffer.progress;
|
||||
return 1;
|
||||
@@ -137,12 +163,13 @@ void receiveChildInfo(void) {
|
||||
if (server.child_info_pipe[0] == -1) return;
|
||||
|
||||
size_t cow;
|
||||
monotime cow_updated;
|
||||
size_t keys;
|
||||
double progress;
|
||||
childInfoType information_type;
|
||||
|
||||
/* Drain the pipe and update child info so that we get the final message. */
|
||||
while (readChildInfo(&information_type, &cow, &keys, &progress)) {
|
||||
updateChildInfo(information_type, cow, keys, progress);
|
||||
while (readChildInfo(&information_type, &cow, &cow_updated, &keys, &progress)) {
|
||||
updateChildInfo(information_type, cow, cow_updated, keys, progress);
|
||||
}
|
||||
}
|
||||
|
||||
+168
-124
@@ -55,7 +55,7 @@ void clusterSendFail(char *nodename);
|
||||
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
|
||||
void clusterUpdateState(void);
|
||||
int clusterNodeGetSlotBit(clusterNode *n, int slot);
|
||||
sds clusterGenNodesDescription(int filter);
|
||||
sds clusterGenNodesDescription(int filter, int use_pport);
|
||||
clusterNode *clusterLookupNode(const char *name);
|
||||
int clusterNodeAddSlave(clusterNode *master, clusterNode *slave);
|
||||
int clusterAddSlot(clusterNode *n, int slot);
|
||||
@@ -190,6 +190,9 @@ int clusterLoadConfig(char *filename) {
|
||||
* base port. */
|
||||
n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR;
|
||||
|
||||
/* The plaintext port for client in a TLS cluster (n->pport) is not
|
||||
* stored in nodes.conf. It is received later over the bus protocol. */
|
||||
|
||||
/* Parse flags */
|
||||
p = s = argv[2];
|
||||
while(p) {
|
||||
@@ -336,7 +339,7 @@ int clusterSaveConfig(int do_fsync) {
|
||||
|
||||
/* Get the nodes description and concatenate our "vars" directive to
|
||||
* save currentEpoch and lastVoteEpoch. */
|
||||
ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE);
|
||||
ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE, 0);
|
||||
ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n",
|
||||
(unsigned long long) server.cluster->currentEpoch,
|
||||
(unsigned long long) server.cluster->lastVoteEpoch);
|
||||
@@ -355,7 +358,7 @@ int clusterSaveConfig(int do_fsync) {
|
||||
if (write(fd,ci,sdslen(ci)) != (ssize_t)sdslen(ci)) goto err;
|
||||
if (do_fsync) {
|
||||
server.cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG;
|
||||
fsync(fd);
|
||||
if (fsync(fd) == -1) goto err;
|
||||
}
|
||||
|
||||
/* Truncate the file if needed to remove the final \n padding that
|
||||
@@ -437,6 +440,26 @@ int clusterLockConfig(char *filename) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Derives our ports to be announced in the cluster bus. */
|
||||
void deriveAnnouncedPorts(int *announced_port, int *announced_pport,
|
||||
int *announced_cport) {
|
||||
int port = server.tls_cluster ? server.tls_port : server.port;
|
||||
/* Default announced ports. */
|
||||
*announced_port = port;
|
||||
*announced_pport = server.tls_cluster ? server.port : 0;
|
||||
*announced_cport = port + CLUSTER_PORT_INCR;
|
||||
/* Config overriding announced ports. */
|
||||
if (server.tls_cluster && server.cluster_announce_tls_port) {
|
||||
*announced_port = server.cluster_announce_tls_port;
|
||||
*announced_pport = server.cluster_announce_port;
|
||||
} else if (server.cluster_announce_port) {
|
||||
*announced_port = server.cluster_announce_port;
|
||||
}
|
||||
if (server.cluster_announce_bus_port) {
|
||||
*announced_cport = server.cluster_announce_bus_port;
|
||||
}
|
||||
}
|
||||
|
||||
/* Some flags (currently just the NOFAILOVER flag) may need to be updated
|
||||
* in the "myself" node based on the current configuration of the node,
|
||||
* that may change at runtime via CONFIG SET. This function changes the
|
||||
@@ -499,7 +522,7 @@ void clusterInit(void) {
|
||||
if (saveconf) clusterSaveConfigOrDie(1);
|
||||
|
||||
/* We need a listening TCP port for our cluster messaging needs. */
|
||||
server.cfd_count = 0;
|
||||
server.cfd.count = 0;
|
||||
|
||||
/* Port sanity check II
|
||||
* The other handshake port check is triggered too late to stop
|
||||
@@ -512,19 +535,13 @@ void clusterInit(void) {
|
||||
"Your Redis port number must be 55535 or less.");
|
||||
exit(1);
|
||||
}
|
||||
if (listenToPort(port+CLUSTER_PORT_INCR,
|
||||
server.cfd,&server.cfd_count) == C_ERR)
|
||||
{
|
||||
if (listenToPort(port+CLUSTER_PORT_INCR, &server.cfd) == C_ERR) {
|
||||
/* Note: the following log text is matched by the test suite. */
|
||||
serverLog(LL_WARNING, "Failed listening on port %u (cluster), aborting.", port);
|
||||
exit(1);
|
||||
} else {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.cfd_count; j++) {
|
||||
if (aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE,
|
||||
clusterAcceptHandler, NULL) == AE_ERR)
|
||||
serverPanic("Unrecoverable error creating Redis Cluster "
|
||||
"file event.");
|
||||
}
|
||||
}
|
||||
if (createSocketAcceptHandler(&server.cfd, clusterAcceptHandler) != C_OK) {
|
||||
serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
|
||||
}
|
||||
|
||||
/* The slots -> keys map is a radix tree. Initialize it here. */
|
||||
@@ -532,16 +549,12 @@ void clusterInit(void) {
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
|
||||
/* Set myself->port / cport to my listening ports, we'll just need to
|
||||
/* Set myself->port/cport/pport to my listening ports, we'll just need to
|
||||
* discover the IP address via MEET messages. */
|
||||
myself->port = port;
|
||||
myself->cport = port+CLUSTER_PORT_INCR;
|
||||
if (server.cluster_announce_port)
|
||||
myself->port = server.cluster_announce_port;
|
||||
if (server.cluster_announce_bus_port)
|
||||
myself->cport = server.cluster_announce_bus_port;
|
||||
deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport);
|
||||
|
||||
server.cluster->mf_end = 0;
|
||||
server.cluster->mf_slave = NULL;
|
||||
resetManualFailover();
|
||||
clusterUpdateMyselfFlags();
|
||||
}
|
||||
@@ -697,6 +710,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
connNonBlock(conn);
|
||||
connEnableTcpNoDelay(conn);
|
||||
connKeepAlive(conn,server.cluster_node_timeout / 1000 * 2);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
|
||||
@@ -790,6 +804,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
||||
memset(node->ip,0,sizeof(node->ip));
|
||||
node->port = 0;
|
||||
node->cport = 0;
|
||||
node->pport = 0;
|
||||
node->fail_reports = listCreate();
|
||||
node->voted_time = 0;
|
||||
node->orphaned_time = 0;
|
||||
@@ -1496,6 +1511,7 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
if (node->link) freeClusterLink(node->link);
|
||||
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
|
||||
node->port = ntohs(g->port);
|
||||
node->pport = ntohs(g->pport);
|
||||
node->cport = ntohs(g->cport);
|
||||
node->flags &= ~CLUSTER_NODE_NOADDR;
|
||||
}
|
||||
@@ -1517,6 +1533,7 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
node = createClusterNode(g->nodename, flags);
|
||||
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
|
||||
node->port = ntohs(g->port);
|
||||
node->pport = ntohs(g->pport);
|
||||
node->cport = ntohs(g->cport);
|
||||
clusterAddNode(node);
|
||||
}
|
||||
@@ -1556,6 +1573,7 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
|
||||
{
|
||||
char ip[NET_IP_STR_LEN] = {0};
|
||||
int port = ntohs(hdr->port);
|
||||
int pport = ntohs(hdr->pport);
|
||||
int cport = ntohs(hdr->cport);
|
||||
|
||||
/* We don't proceed if the link is the same as the sender link, as this
|
||||
@@ -1567,12 +1585,13 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
|
||||
if (link == node->link) return 0;
|
||||
|
||||
nodeIp2String(ip,link,hdr->myip);
|
||||
if (node->port == port && node->cport == cport &&
|
||||
if (node->port == port && node->cport == cport && node->pport == pport &&
|
||||
strcmp(ip,node->ip) == 0) return 0;
|
||||
|
||||
/* IP / port is different, update it. */
|
||||
memcpy(node->ip,ip,sizeof(ip));
|
||||
node->port = port;
|
||||
node->pport = pport;
|
||||
node->cport = cport;
|
||||
if (node->link) freeClusterLink(node->link);
|
||||
node->flags &= ~CLUSTER_NODE_NOADDR;
|
||||
@@ -1618,7 +1637,7 @@ void clusterSetNodeAsMaster(clusterNode *n) {
|
||||
* case we receive the info via an UPDATE packet. */
|
||||
void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) {
|
||||
int j;
|
||||
clusterNode *curmaster, *newmaster = NULL;
|
||||
clusterNode *curmaster = NULL, *newmaster = NULL;
|
||||
/* The dirty slots list is a list of slots for which we lose the ownership
|
||||
* while having still keys inside. This usually happens after a failover
|
||||
* or after a manual cluster reconfiguration operated by the admin.
|
||||
@@ -1629,6 +1648,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
uint16_t dirty_slots[CLUSTER_SLOTS];
|
||||
int dirty_slots_count = 0;
|
||||
|
||||
/* We should detect if sender is new master of our shard.
|
||||
* We will know it if all our slots were migrated to sender, and sender
|
||||
* has no slots except ours */
|
||||
int sender_slots = 0;
|
||||
int migrated_our_slots = 0;
|
||||
|
||||
/* Here we set curmaster to this node or the node this node
|
||||
* replicates to if it's a slave. In the for loop we are
|
||||
* interested to check if slots are taken away from curmaster. */
|
||||
@@ -1641,6 +1666,8 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
if (bitmapTestBit(slots,j)) {
|
||||
sender_slots++;
|
||||
|
||||
/* The slot is already bound to the sender of this message. */
|
||||
if (server.cluster->slots[j] == sender) continue;
|
||||
|
||||
@@ -1667,8 +1694,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
dirty_slots_count++;
|
||||
}
|
||||
|
||||
if (server.cluster->slots[j] == curmaster)
|
||||
if (server.cluster->slots[j] == curmaster) {
|
||||
newmaster = sender;
|
||||
migrated_our_slots++;
|
||||
}
|
||||
clusterDelSlot(j);
|
||||
clusterAddSlot(sender,j);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
@@ -1691,7 +1720,9 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
* master.
|
||||
* 2) We are a slave and our master is left without slots. We need
|
||||
* to replicate to the new slots owner. */
|
||||
if (newmaster && curmaster->numslots == 0) {
|
||||
if (newmaster && curmaster->numslots == 0 &&
|
||||
(server.cluster_allow_replica_migration ||
|
||||
sender_slots == migrated_our_slots)) {
|
||||
serverLog(LL_WARNING,
|
||||
"Configuration change detected. Reconfiguring myself "
|
||||
"as a replica of %.40s", sender->name);
|
||||
@@ -1819,7 +1850,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
nodeIsSlave(myself) &&
|
||||
myself->slaveof == sender &&
|
||||
hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED &&
|
||||
server.cluster->mf_master_offset == 0)
|
||||
server.cluster->mf_master_offset == -1)
|
||||
{
|
||||
server.cluster->mf_master_offset = sender->repl_offset;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
|
||||
@@ -1870,6 +1901,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE);
|
||||
nodeIp2String(node->ip,link,hdr->myip);
|
||||
node->port = ntohs(hdr->port);
|
||||
node->pport = ntohs(hdr->pport);
|
||||
node->cport = ntohs(hdr->cport);
|
||||
clusterAddNode(node);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
|
||||
@@ -1932,6 +1964,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
link->node->flags |= CLUSTER_NODE_NOADDR;
|
||||
link->node->ip[0] = '\0';
|
||||
link->node->port = 0;
|
||||
link->node->pport = 0;
|
||||
link->node->cport = 0;
|
||||
freeClusterLink(link);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
|
||||
@@ -2431,19 +2464,16 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
|
||||
hdr->myip[NET_IP_STR_LEN-1] = '\0';
|
||||
}
|
||||
|
||||
/* Handle cluster-announce-port as well. */
|
||||
int port = server.tls_cluster ? server.tls_port : server.port;
|
||||
int announced_port = server.cluster_announce_port ?
|
||||
server.cluster_announce_port : port;
|
||||
int announced_cport = server.cluster_announce_bus_port ?
|
||||
server.cluster_announce_bus_port :
|
||||
(port + CLUSTER_PORT_INCR);
|
||||
/* Handle cluster-announce-[tls-|bus-]port. */
|
||||
int announced_port, announced_pport, announced_cport;
|
||||
deriveAnnouncedPorts(&announced_port, &announced_pport, &announced_cport);
|
||||
|
||||
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
|
||||
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
|
||||
if (myself->slaveof != NULL)
|
||||
memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN);
|
||||
hdr->port = htons(announced_port);
|
||||
hdr->pport = htons(announced_pport);
|
||||
hdr->cport = htons(announced_cport);
|
||||
hdr->flags = htons(myself->flags);
|
||||
hdr->state = server.cluster->state;
|
||||
@@ -2500,6 +2530,7 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
|
||||
gossip->port = htons(n->port);
|
||||
gossip->cport = htons(n->cport);
|
||||
gossip->flags = htons(n->flags);
|
||||
gossip->pport = htons(n->pport);
|
||||
gossip->notused1 = 0;
|
||||
}
|
||||
|
||||
@@ -3098,7 +3129,7 @@ void clusterFailoverReplaceYourMaster(void) {
|
||||
/* This function is called if we are a slave node and our master serving
|
||||
* a non-zero amount of hash slots is in FAIL state.
|
||||
*
|
||||
* The gaol of this function is:
|
||||
* The goal of this function is:
|
||||
* 1) To check if we are able to perform a failover, is our data updated?
|
||||
* 2) Try to get elected by masters.
|
||||
* 3) Perform the failover informing all the other nodes.
|
||||
@@ -3143,7 +3174,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set data_age to the number of seconds we are disconnected from
|
||||
/* Set data_age to the number of milliseconds we are disconnected from
|
||||
* the master. */
|
||||
if (server.repl_state == REPL_STATE_CONNECTED) {
|
||||
data_age = (mstime_t)(server.unixtime - server.master->lastinteraction)
|
||||
@@ -3421,13 +3452,15 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
* The function can be used both to initialize the manual failover state at
|
||||
* startup or to abort a manual failover in progress. */
|
||||
void resetManualFailover(void) {
|
||||
if (server.cluster->mf_end) {
|
||||
checkClientPauseTimeoutAndReturnIfPaused();
|
||||
if (server.cluster->mf_slave) {
|
||||
/* We were a master failing over, so we paused clients. Regardless
|
||||
* of the outcome we unpause now to allow traffic again. */
|
||||
unpauseClients();
|
||||
}
|
||||
server.cluster->mf_end = 0; /* No manual failover in progress. */
|
||||
server.cluster->mf_can_start = 0;
|
||||
server.cluster->mf_slave = NULL;
|
||||
server.cluster->mf_master_offset = 0;
|
||||
server.cluster->mf_master_offset = -1;
|
||||
}
|
||||
|
||||
/* If a manual failover timed out, abort it. */
|
||||
@@ -3448,7 +3481,7 @@ void clusterHandleManualFailover(void) {
|
||||
* next steps are performed by clusterHandleSlaveFailover(). */
|
||||
if (server.cluster->mf_can_start) return;
|
||||
|
||||
if (server.cluster->mf_master_offset == 0) return; /* Wait for offset... */
|
||||
if (server.cluster->mf_master_offset == -1) return; /* Wait for offset... */
|
||||
|
||||
if (server.cluster->mf_master_offset == replicationGetSlaveOffset()) {
|
||||
/* Our replication offset matches the master replication offset
|
||||
@@ -3638,7 +3671,6 @@ void clusterCron(void) {
|
||||
now - node->link->ctime >
|
||||
server.cluster_node_timeout && /* was not already reconnected */
|
||||
node->ping_sent && /* we already sent a ping */
|
||||
node->pong_received < node->ping_sent && /* still waiting pong */
|
||||
/* and we are waiting for the pong more than timeout/2 */
|
||||
ping_delay > server.cluster_node_timeout/2 &&
|
||||
/* and in such interval we are not seeing any traffic at all. */
|
||||
@@ -3721,7 +3753,8 @@ void clusterCron(void) {
|
||||
* the orphaned masters. Note that it does not make sense to try
|
||||
* a migration if there is no master with at least *two* working
|
||||
* slaves. */
|
||||
if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves)
|
||||
if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves &&
|
||||
server.cluster_allow_replica_migration)
|
||||
clusterHandleSlaveMigration(max_slaves);
|
||||
}
|
||||
|
||||
@@ -3831,7 +3864,7 @@ int clusterNodeSetSlotBit(clusterNode *n, int slot) {
|
||||
* However new masters with slots assigned are considered valid
|
||||
* migration targets if the rest of the cluster is not a slave-less.
|
||||
*
|
||||
* See https://github.com/antirez/redis/issues/3043 for more info. */
|
||||
* See https://github.com/redis/redis/issues/3043 for more info. */
|
||||
if (n->numslots == 1 && clusterMastersHaveSlaves())
|
||||
n->flags |= CLUSTER_NODE_MIGRATE_TO;
|
||||
}
|
||||
@@ -4139,15 +4172,16 @@ sds representClusterNodeFlags(sds ci, uint16_t flags) {
|
||||
* See clusterGenNodesDescription() top comment for more information.
|
||||
*
|
||||
* The function returns the string representation as an SDS string. */
|
||||
sds clusterGenNodeDescription(clusterNode *node) {
|
||||
sds clusterGenNodeDescription(clusterNode *node, int use_pport) {
|
||||
int j, start;
|
||||
sds ci;
|
||||
int port = use_pport && node->pport ? node->pport : node->port;
|
||||
|
||||
/* Node coordinates */
|
||||
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
|
||||
ci = sdscatfmt(ci," %s:%i@%i ",
|
||||
node->ip,
|
||||
node->port,
|
||||
port,
|
||||
node->cport);
|
||||
|
||||
/* Flags */
|
||||
@@ -4258,10 +4292,13 @@ void clusterGenNodesSlotsInfo(int filter) {
|
||||
* include all the known nodes in the representation, including nodes in
|
||||
* the HANDSHAKE state.
|
||||
*
|
||||
* Setting use_pport to 1 in a TLS cluster makes the result contain the
|
||||
* plaintext client port rather then the TLS client port of each node.
|
||||
*
|
||||
* The representation obtained using this function is used for the output
|
||||
* of the CLUSTER NODES function, and as format for the cluster
|
||||
* configuration file (nodes.conf) for a given node. */
|
||||
sds clusterGenNodesDescription(int filter) {
|
||||
sds clusterGenNodesDescription(int filter, int use_pport) {
|
||||
sds ci = sdsempty(), ni;
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
@@ -4274,7 +4311,7 @@ sds clusterGenNodesDescription(int filter) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
|
||||
if (node->flags & filter) continue;
|
||||
ni = clusterGenNodeDescription(node);
|
||||
ni = clusterGenNodeDescription(node, use_pport);
|
||||
ci = sdscatsds(ci,ni);
|
||||
sdsfree(ni);
|
||||
ci = sdscatlen(ci,"\n",1);
|
||||
@@ -4321,7 +4358,37 @@ int getSlotOrReply(client *c, robj *o) {
|
||||
return (int) slot;
|
||||
}
|
||||
|
||||
void clusterReplyMultiBulkSlots(client *c) {
|
||||
void addNodeReplyForClusterSlot(client *c, clusterNode *node, int start_slot, int end_slot) {
|
||||
int i, nested_elements = 3; /* slots (2) + master addr (1) */
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
addReplyLongLong(c, start_slot);
|
||||
addReplyLongLong(c, end_slot);
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
/* Report non-TLS ports to non-TLS client in TLS cluster if available. */
|
||||
int use_pport = (server.tls_cluster &&
|
||||
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
|
||||
addReplyLongLong(c, use_pport && node->pport ? node->pport : node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
|
||||
/* Remaining nodes in reply are replicas for slot range */
|
||||
for (i = 0; i < node->numslaves; i++) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation. */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
/* Report slave's non-TLS port to non-TLS client in TLS cluster */
|
||||
addReplyLongLong(c, (use_pport && node->slaves[i]->pport ?
|
||||
node->slaves[i]->pport :
|
||||
node->slaves[i]->port));
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
}
|
||||
|
||||
void clusterReplyMultiBulkSlots(client * c) {
|
||||
/* Format: 1) 1) start slot
|
||||
* 2) end slot
|
||||
* 3) 1) master IP
|
||||
@@ -4332,69 +4399,29 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
* 3) node ID
|
||||
* ... continued until done
|
||||
*/
|
||||
|
||||
int num_masters = 0;
|
||||
clusterNode *n = NULL;
|
||||
int num_masters = 0, start = -1;
|
||||
void *slot_replylen = addReplyDeferredLen(c);
|
||||
|
||||
dictEntry *de;
|
||||
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
int j = 0, start = -1;
|
||||
int i, nested_elements = 0;
|
||||
|
||||
/* Skip slaves (that are iterated when producing the output of their
|
||||
* master) and masters not serving any slot. */
|
||||
if (!nodeIsMaster(node) || node->numslots == 0) continue;
|
||||
|
||||
for(i = 0; i < node->numslaves; i++) {
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
nested_elements++;
|
||||
for (int i = 0; i <= CLUSTER_SLOTS; i++) {
|
||||
/* Find start node and slot id. */
|
||||
if (n == NULL) {
|
||||
if (i == CLUSTER_SLOTS) break;
|
||||
n = server.cluster->slots[i];
|
||||
start = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
int bit, i;
|
||||
|
||||
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
addReplyArrayLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
/* If slot exists in output map, add to it's list.
|
||||
* else, create a new output map for this slot */
|
||||
if (start == j-1) {
|
||||
addReplyLongLong(c, start); /* only one slot; low==high */
|
||||
addReplyLongLong(c, start);
|
||||
} else {
|
||||
addReplyLongLong(c, start); /* low */
|
||||
addReplyLongLong(c, j-1); /* high */
|
||||
}
|
||||
start = -1;
|
||||
|
||||
/* First node reply position is always the master */
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
|
||||
/* Remaining nodes in reply are replicas for slot range */
|
||||
for (i = 0; i < node->numslaves; i++) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
}
|
||||
num_masters++;
|
||||
}
|
||||
/* Add cluster slots info when occur different node with start
|
||||
* or end of slot. */
|
||||
if (i == CLUSTER_SLOTS || n != server.cluster->slots[i]) {
|
||||
addNodeReplyForClusterSlot(c, n, start, i-1);
|
||||
num_masters++;
|
||||
if (i == CLUSTER_SLOTS) break;
|
||||
n = server.cluster->slots[i];
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c, slot_replylen, num_masters);
|
||||
}
|
||||
|
||||
@@ -4483,7 +4510,11 @@ NULL
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
||||
/* CLUSTER NODES */
|
||||
sds nodes = clusterGenNodesDescription(0);
|
||||
/* Report plaintext ports, only if cluster is TLS but client is known to
|
||||
* be non-TLS). */
|
||||
int use_pport = (server.tls_cluster &&
|
||||
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
|
||||
sds nodes = clusterGenNodesDescription(0, use_pport);
|
||||
addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
|
||||
sdsfree(nodes);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
|
||||
@@ -4859,9 +4890,12 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
/* Use plaintext port if cluster is TLS but client is non-TLS. */
|
||||
int use_pport = (server.tls_cluster &&
|
||||
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
|
||||
addReplyArrayLen(c,n->numslaves);
|
||||
for (j = 0; j < n->numslaves; j++) {
|
||||
sds ni = clusterGenNodeDescription(n->slaves[j]);
|
||||
sds ni = clusterGenNodeDescription(n->slaves[j], use_pport);
|
||||
addReplyBulkCString(c,ni);
|
||||
sdsfree(ni);
|
||||
}
|
||||
@@ -5148,7 +5182,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr)) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr,NULL)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@@ -5333,13 +5367,16 @@ void migrateCommand(client *c) {
|
||||
}
|
||||
j++;
|
||||
password = c->argv[j]->ptr;
|
||||
redactClientCommandArgument(c,j);
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"auth2")) {
|
||||
if (moreargs < 2) {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
username = c->argv[++j]->ptr;
|
||||
redactClientCommandArgument(c,j);
|
||||
password = c->argv[++j]->ptr;
|
||||
redactClientCommandArgument(c,j);
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
|
||||
if (sdslen(c->argv[3]->ptr) != 0) {
|
||||
addReplyError(c,
|
||||
@@ -5437,9 +5474,10 @@ try_again:
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
|
||||
/* Relocate valid (non expired) keys into the array in successive
|
||||
/* Relocate valid (non expired) keys and values into the array in successive
|
||||
* positions to remove holes created by the keys that were present
|
||||
* in the first lookup but are now expired after the second lookup. */
|
||||
ov[non_expired] = ov[j];
|
||||
kv[non_expired++] = kv[j];
|
||||
|
||||
serverAssertWithInfo(c,NULL,
|
||||
@@ -5630,10 +5668,9 @@ socket_err:
|
||||
|
||||
/* Cleanup we want to do if no retry is attempted. */
|
||||
zfree(ov); zfree(kv);
|
||||
addReplySds(c,
|
||||
sdscatprintf(sdsempty(),
|
||||
"-IOERR error or timeout %s to target instance\r\n",
|
||||
write_error ? "writing" : "reading"));
|
||||
addReplyErrorSds(c, sdscatprintf(sdsempty(),
|
||||
"-IOERR error or timeout %s to target instance",
|
||||
write_error ? "writing" : "reading"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5832,18 +5869,14 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
* cluster is down. */
|
||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
||||
return NULL;
|
||||
} else if ((cmd->flags & CMD_WRITE) && !(cmd->proc == evalCommand)
|
||||
&& !(cmd->proc == evalShaCommand))
|
||||
{
|
||||
/* The cluster is configured to allow read only commands
|
||||
* but this command is neither readonly, nor EVAL or
|
||||
* EVALSHA. */
|
||||
} else if (cmd->flags & CMD_WRITE) {
|
||||
/* The cluster is configured to allow read only commands */
|
||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_RO_STATE;
|
||||
return NULL;
|
||||
} else {
|
||||
/* Fall through and allow the command to be executed:
|
||||
* this happens when server.cluster_allow_reads_when_down is
|
||||
* true and the command is a readonly command or EVAL / EVALSHA. */
|
||||
* true and the command is not a write command */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5884,7 +5917,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
|
||||
if (c->flags & CLIENT_READONLY &&
|
||||
(!is_write_command || cmd->proc == evalCommand || cmd->proc == evalShaCommand) &&
|
||||
!is_write_command &&
|
||||
nodeIsSlave(myself) &&
|
||||
myself->slaveof == n)
|
||||
{
|
||||
@@ -5921,10 +5954,15 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
|
||||
} else if (error_code == CLUSTER_REDIR_MOVED ||
|
||||
error_code == CLUSTER_REDIR_ASK)
|
||||
{
|
||||
/* Redirect to IP:port. Include plaintext port if cluster is TLS but
|
||||
* client is non-TLS. */
|
||||
int use_pport = (server.tls_cluster &&
|
||||
c->conn && connGetType(c->conn) != CONN_TYPE_TLS);
|
||||
int port = use_pport && n->pport ? n->pport : n->port;
|
||||
addReplyErrorSds(c,sdscatprintf(sdsempty(),
|
||||
"-%s %d %s:%d",
|
||||
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
||||
hashslot,n->ip,n->port));
|
||||
hashslot, n->ip, port));
|
||||
} else {
|
||||
serverPanic("getNodeByQuery() unknown error.");
|
||||
}
|
||||
@@ -5945,7 +5983,8 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
if (c->flags & CLIENT_BLOCKED &&
|
||||
(c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM))
|
||||
c->btype == BLOCKED_STREAM ||
|
||||
c->btype == BLOCKED_MODULE))
|
||||
{
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
@@ -5959,6 +5998,11 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* If the client is blocked on module, but ont on a specific key,
|
||||
* don't unblock it (except for the CLSUTER_FAIL case above). */
|
||||
if (c->btype == BLOCKED_MODULE && !moduleClientIsBlockedOnKeys(c))
|
||||
return 0;
|
||||
|
||||
/* All keys must belong to the same slot, so check first key only. */
|
||||
di = dictGetIterator(c->bpop.keys);
|
||||
if ((de = dictNext(di)) != NULL) {
|
||||
|
||||
+8
-4
@@ -135,7 +135,9 @@ typedef struct clusterNode {
|
||||
mstime_t orphaned_time; /* Starting time of orphaned master condition */
|
||||
long long repl_offset; /* Last known repl offset for this node. */
|
||||
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
|
||||
int port; /* Latest known clients port of this node */
|
||||
int port; /* Latest known clients port (TLS or plain). */
|
||||
int pport; /* Latest known clients plaintext port. Only used
|
||||
if the main clients port is for TLS. */
|
||||
int cport; /* Latest known cluster port of this node. */
|
||||
clusterLink *link; /* TCP/IP link with this node */
|
||||
list *fail_reports; /* List of nodes signaling this as failing */
|
||||
@@ -168,7 +170,7 @@ typedef struct clusterState {
|
||||
clusterNode *mf_slave; /* Slave performing the manual failover. */
|
||||
/* Manual failover state of slave. */
|
||||
long long mf_master_offset; /* Master offset the slave needs to start MF
|
||||
or zero if still not received. */
|
||||
or -1 if still not received. */
|
||||
int mf_can_start; /* If non-zero signal that the manual failover
|
||||
can start requesting masters vote. */
|
||||
/* The following fields are used by masters to take state on elections. */
|
||||
@@ -194,7 +196,8 @@ typedef struct {
|
||||
uint16_t port; /* base port last time it was seen */
|
||||
uint16_t cport; /* cluster port last time it was seen */
|
||||
uint16_t flags; /* node->flags copy */
|
||||
uint32_t notused1;
|
||||
uint16_t pport; /* plaintext-port, when base port is TLS */
|
||||
uint16_t notused1;
|
||||
} clusterMsgDataGossip;
|
||||
|
||||
typedef struct {
|
||||
@@ -267,7 +270,8 @@ typedef struct {
|
||||
unsigned char myslots[CLUSTER_SLOTS/8];
|
||||
char slaveof[CLUSTER_NAMELEN];
|
||||
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
|
||||
char notused1[34]; /* 34 bytes reserved for future usage. */
|
||||
char notused1[32]; /* 32 bytes reserved for future usage. */
|
||||
uint16_t pport; /* Sender TCP plaintext port, if base port is TLS */
|
||||
uint16_t cport; /* Sender TCP cluster bus port */
|
||||
uint16_t flags; /* Sender node flags */
|
||||
unsigned char state; /* Cluster state from the POV of the sender */
|
||||
|
||||
+134
-127
@@ -229,8 +229,6 @@ typedef union typeData {
|
||||
typedef struct typeInterface {
|
||||
/* Called on server start, to init the server with default value */
|
||||
void (*init)(typeData data);
|
||||
/* Called on server start, should return 1 on success, 0 on error and should set err */
|
||||
int (*load)(typeData data, sds *argc, int argv, const char **err);
|
||||
/* Called on server startup and CONFIG SET, returns 1 on success, 0 on error
|
||||
* and can set a verbose err string, update is true when called from CONFIG SET */
|
||||
int (*set)(typeData data, sds value, int update, const char **err);
|
||||
@@ -243,11 +241,16 @@ typedef struct typeInterface {
|
||||
typedef struct standardConfig {
|
||||
const char *name; /* The user visible name of this config */
|
||||
const char *alias; /* An alias that can also be used for this config */
|
||||
const int modifiable; /* Can this value be updated by CONFIG SET? */
|
||||
const unsigned int flags; /* Flags for this specific config */
|
||||
typeInterface interface; /* The function pointers that define the type interface */
|
||||
typeData data; /* The type specific data exposed used by the interface */
|
||||
} standardConfig;
|
||||
|
||||
#define MODIFIABLE_CONFIG 0 /* This is the implied default for a standard
|
||||
* config, which is mutable. */
|
||||
#define IMMUTABLE_CONFIG (1ULL<<0) /* Can this value only be set at startup? */
|
||||
#define SENSITIVE_CONFIG (1ULL<<1) /* Does this value contain sensitive information */
|
||||
|
||||
standardConfig configs[];
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -385,6 +388,7 @@ void initConfigValues() {
|
||||
}
|
||||
|
||||
void loadServerConfigFromString(char *config) {
|
||||
char buf[1024];
|
||||
const char *err = NULL;
|
||||
int linenum = 0, totlines, i;
|
||||
int slaveof_linenum = 0;
|
||||
@@ -504,8 +508,6 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
|
||||
loadServerConfig(argv[1], 0, NULL);
|
||||
} else if ((!strcasecmp(argv[0],"client-query-buffer-limit")) && argc == 2) {
|
||||
server.client_max_querybuf_len = memtoll(argv[1],NULL);
|
||||
} else if ((!strcasecmp(argv[0],"slaveof") ||
|
||||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
|
||||
slaveof_linenum = linenum;
|
||||
@@ -521,26 +523,6 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "Invalid master port"; goto loaderr;
|
||||
}
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
if (sdslen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
|
||||
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
||||
goto loaderr;
|
||||
}
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. The only thing we do
|
||||
* additionally is to remember the cleartext password in this
|
||||
* case, for backward compatibility with Redis <= 5. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sdsfree(server.requirepass);
|
||||
server.requirepass = NULL;
|
||||
if (sdslen(argv[1])) {
|
||||
sds aclop = sdscatlen(sdsnew(">"), argv[1], sdslen(argv[1]));
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
server.requirepass = sdsdup(argv[1]);
|
||||
} else {
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
|
||||
/* DEAD OPTION */
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
|
||||
@@ -607,7 +589,6 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"user") && argc >= 2) {
|
||||
int argc_err;
|
||||
if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
|
||||
char buf[1024];
|
||||
const char *errmsg = ACLSetUserStringError();
|
||||
snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
|
||||
argv[argc_err],errmsg);
|
||||
@@ -640,6 +621,10 @@ void loadServerConfigFromString(char *config) {
|
||||
goto loaderr;
|
||||
}
|
||||
|
||||
/* To ensure backward compatibility and work while hz is out of range */
|
||||
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
|
||||
if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
|
||||
|
||||
sdsfreesplitres(lines,totlines);
|
||||
return;
|
||||
|
||||
@@ -736,9 +721,13 @@ void configSetCommand(client *c) {
|
||||
|
||||
/* Iterate the configs that are standard */
|
||||
for (standardConfig *config = configs; config->name != NULL; config++) {
|
||||
if(config->modifiable && (!strcasecmp(c->argv[2]->ptr,config->name) ||
|
||||
if (!(config->flags & IMMUTABLE_CONFIG) &&
|
||||
(!strcasecmp(c->argv[2]->ptr,config->name) ||
|
||||
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
|
||||
{
|
||||
if (config->flags & SENSITIVE_CONFIG) {
|
||||
redactClientCommandArgument(c,3);
|
||||
}
|
||||
if (!config->interface.set(config->data,o->ptr,1,&errstr)) {
|
||||
goto badfmt;
|
||||
}
|
||||
@@ -750,23 +739,22 @@ void configSetCommand(client *c) {
|
||||
if (0) { /* this starts the config_set macros else-if chain. */
|
||||
|
||||
/* Special fields that can't be handled with general macros. */
|
||||
config_set_special_field("requirepass") {
|
||||
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. The only thing we do
|
||||
* additionally is to remember the cleartext password in this
|
||||
* case, for backward compatibility with Redis <= 5. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sdsfree(server.requirepass);
|
||||
server.requirepass = NULL;
|
||||
if (sdslen(o->ptr)) {
|
||||
sds aclop = sdscatlen(sdsnew(">"), o->ptr, sdslen(o->ptr));
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
server.requirepass = sdsdup(o->ptr);
|
||||
} else {
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
config_set_special_field("bind") {
|
||||
int vlen;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
|
||||
if (vlen < 1 || vlen > CONFIG_BINDADDR_MAX) {
|
||||
addReplyError(c, "Too many bind addresses specified.");
|
||||
sdsfreesplitres(v, vlen);
|
||||
return;
|
||||
}
|
||||
|
||||
if (changeBindAddr(v, vlen) == C_ERR) {
|
||||
addReplyError(c, "Failed to bind to specified addresses.");
|
||||
sdsfreesplitres(v, vlen);
|
||||
return;
|
||||
}
|
||||
sdsfreesplitres(v, vlen);
|
||||
} config_set_special_field("save") {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
@@ -876,10 +864,6 @@ void configSetCommand(client *c) {
|
||||
enableWatchdog(ll);
|
||||
else
|
||||
disableWatchdog();
|
||||
/* Memory fields.
|
||||
* config_set_memory_field(name,var) */
|
||||
} config_set_memory_field(
|
||||
"client-query-buffer-limit",server.client_max_querybuf_len) {
|
||||
/* Everything else is an error... */
|
||||
} config_set_else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
@@ -959,7 +943,6 @@ void configGetCommand(client *c) {
|
||||
config_get_string_field("logfile",server.logfile);
|
||||
|
||||
/* Numerical values */
|
||||
config_get_numerical_field("client-query-buffer-limit",server.client_max_querybuf_len);
|
||||
config_get_numerical_field("watchdog-period",server.watchdog_period);
|
||||
|
||||
/* Everything we can't handle with macros follows. */
|
||||
@@ -1046,16 +1029,6 @@ void configGetCommand(client *c) {
|
||||
sdsfree(aux);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"requirepass",1)) {
|
||||
addReplyBulkCString(c,"requirepass");
|
||||
sds password = server.requirepass;
|
||||
if (password) {
|
||||
addReplyBulkCBuffer(c,password,sdslen(password));
|
||||
} else {
|
||||
addReplyBulkCString(c,"");
|
||||
}
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern,"oom-score-adj-values",0)) {
|
||||
sds buf = sdsempty();
|
||||
@@ -1408,14 +1381,19 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Note that if there are no save parameters at all, all the current
|
||||
* config line with "save" will be detected as orphaned and deleted,
|
||||
* resulting into no RDB persistence as expected. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
line = sdscatprintf(sdsempty(),"save %ld %d",
|
||||
(long) server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
rewriteConfigRewriteLine(state,"save",line,1);
|
||||
/* Rewrite save parameters, or an empty 'save ""' line to avoid the
|
||||
* defaults from being used.
|
||||
*/
|
||||
if (!server.saveparamslen) {
|
||||
rewriteConfigRewriteLine(state,"save",sdsnew("save \"\""),1);
|
||||
} else {
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
line = sdscatprintf(sdsempty(),"save %ld %d",
|
||||
(long) server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
rewriteConfigRewriteLine(state,"save",line,1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark "save" as processed in case server.saveparamslen is zero. */
|
||||
rewriteConfigMarkAsProcessed(state,"save");
|
||||
}
|
||||
@@ -1564,26 +1542,6 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the requirepass option. */
|
||||
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
|
||||
int force = 1;
|
||||
sds line;
|
||||
sds password = server.requirepass;
|
||||
|
||||
/* If there is no password set, we don't want the requirepass option
|
||||
* to be present in the configuration at all. */
|
||||
if (password == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
}
|
||||
|
||||
line = sdsnew(option);
|
||||
line = sdscatlen(line, " ", 1);
|
||||
line = sdscatsds(line, password);
|
||||
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Glue together the configuration lines in the current configuration
|
||||
* rewrite state into a single string, stripping multiple empty lines. */
|
||||
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
|
||||
@@ -1740,8 +1698,6 @@ int rewriteConfig(char *path, int force_all) {
|
||||
rewriteConfigUserOption(state);
|
||||
rewriteConfigDirOption(state);
|
||||
rewriteConfigSlaveofOption(state,"replicaof");
|
||||
rewriteConfigRequirepassOption(state,"requirepass");
|
||||
rewriteConfigBytesOption(state,"client-query-buffer-limit",server.client_max_querybuf_len,PROTO_MAX_QUERYBUF_LEN);
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
@@ -1771,13 +1727,10 @@ int rewriteConfig(char *path, int force_all) {
|
||||
#define LOADBUF_SIZE 256
|
||||
static char loadbuf[LOADBUF_SIZE];
|
||||
|
||||
#define MODIFIABLE_CONFIG 1
|
||||
#define IMMUTABLE_CONFIG 0
|
||||
|
||||
#define embedCommonConfig(config_name, config_alias, is_modifiable) \
|
||||
#define embedCommonConfig(config_name, config_alias, config_flags) \
|
||||
.name = (config_name), \
|
||||
.alias = (config_alias), \
|
||||
.modifiable = (is_modifiable),
|
||||
.flags = (config_flags),
|
||||
|
||||
#define embedConfigInterface(initfn, setfn, getfn, rewritefn) .interface = { \
|
||||
.init = (initfn), \
|
||||
@@ -1828,8 +1781,8 @@ static void boolConfigRewrite(typeData data, const char *name, struct rewriteCon
|
||||
rewriteConfigYesNoOption(state, name,*(data.yesno.config), data.yesno.default_value);
|
||||
}
|
||||
|
||||
#define createBoolConfig(name, alias, modifiable, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
#define createBoolConfig(name, alias, flags, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, flags) \
|
||||
embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite) \
|
||||
.data.yesno = { \
|
||||
.config = &(config_addr), \
|
||||
@@ -1901,8 +1854,8 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
|
||||
#define ALLOW_EMPTY_STRING 0
|
||||
#define EMPTY_STRING_IS_NULL 1
|
||||
|
||||
#define createStringConfig(name, alias, modifiable, empty_to_null, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
#define createStringConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, flags) \
|
||||
embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite) \
|
||||
.data.string = { \
|
||||
.config = &(config_addr), \
|
||||
@@ -1913,8 +1866,8 @@ static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConf
|
||||
} \
|
||||
}
|
||||
|
||||
#define createSDSConfig(name, alias, modifiable, empty_to_null, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
#define createSDSConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, flags) \
|
||||
embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite) \
|
||||
.data.sds = { \
|
||||
.config = &(config_addr), \
|
||||
@@ -1969,8 +1922,8 @@ static void enumConfigRewrite(typeData data, const char *name, struct rewriteCon
|
||||
rewriteConfigEnumOption(state, name,*(data.enumd.config), data.enumd.enum_value, data.enumd.default_value);
|
||||
}
|
||||
|
||||
#define createEnumConfig(name, alias, modifiable, enum, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
#define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, flags) \
|
||||
embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite) \
|
||||
.data.enumd = { \
|
||||
.config = &(config_addr), \
|
||||
@@ -2123,8 +2076,8 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
|
||||
#define INTEGER_CONFIG 0
|
||||
#define MEMORY_CONFIG 1
|
||||
|
||||
#define embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
#define embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, flags) \
|
||||
embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite) \
|
||||
.data.numeric = { \
|
||||
.lower_bound = (lower), \
|
||||
@@ -2134,71 +2087,71 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
|
||||
.update_fn = (update), \
|
||||
.is_memory = (memory),
|
||||
|
||||
#define createIntConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createIntConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_INT, \
|
||||
.config.i = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createUIntConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createUIntConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_UINT, \
|
||||
.config.ui = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createLongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createLongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_LONG, \
|
||||
.config.l = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createULongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createULongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_ULONG, \
|
||||
.config.ul = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createLongLongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createLongLongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_LONG_LONG, \
|
||||
.config.ll = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createULongLongConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createULongLongConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_ULONG_LONG, \
|
||||
.config.ull = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createSizeTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createSizeTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_SIZE_T, \
|
||||
.config.st = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createSSizeTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createSSizeTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_SSIZE_T, \
|
||||
.config.sst = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createTimeTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createTimeTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_TIME_T, \
|
||||
.config.tt = &(config_addr) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define createOffTConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
#define createOffTConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, memory, is_valid, update) \
|
||||
.numeric_type = NUMERIC_TYPE_OFF_T, \
|
||||
.config.ot = &(config_addr) \
|
||||
} \
|
||||
@@ -2267,6 +2220,20 @@ static int updateHZ(long long val, long long prev, const char **err) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int updatePort(long long val, long long prev, const char **err) {
|
||||
/* Do nothing if port is unchanged */
|
||||
if (val == prev) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (changeListenPort(val, &server.ipfd, acceptTcpHandler) == C_ERR) {
|
||||
*err = "Unable to listen on this port. Check server logs.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int updateJemallocBgThread(int val, int prev, const char **err) {
|
||||
UNUSED(prev);
|
||||
UNUSED(err);
|
||||
@@ -2368,6 +2335,18 @@ static int updateOOMScoreAdj(int val, int prev, const char **err) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int updateRequirePass(sds val, sds prev, const char **err) {
|
||||
UNUSED(prev);
|
||||
UNUSED(err);
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. The only thing we do
|
||||
* additionally is to remember the cleartext password in this
|
||||
* case, for backward compatibility with Redis <= 5. */
|
||||
ACLUpdateDefaultUserPassword(val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
static int updateTlsCfg(char *val, char *prev, const char **err) {
|
||||
UNUSED(val);
|
||||
@@ -2393,6 +2372,27 @@ static int updateTlsCfgInt(long long val, long long prev, const char **err) {
|
||||
UNUSED(prev);
|
||||
return updateTlsCfg(NULL, NULL, err);
|
||||
}
|
||||
|
||||
static int updateTLSPort(long long val, long long prev, const char **err) {
|
||||
/* Do nothing if port is unchanged */
|
||||
if (val == prev) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Configure TLS if tls is enabled */
|
||||
if (prev == 0 && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
|
||||
*err = "Unable to update TLS configuration. Check server logs.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (changeListenPort(val, &server.tlsfd, acceptTLSHandler) == C_ERR) {
|
||||
*err = "Unable to listen on this port. Check server logs.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* USE_OPENSSL */
|
||||
|
||||
standardConfig configs[] = {
|
||||
@@ -2438,13 +2438,15 @@ standardConfig configs[] = {
|
||||
createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, server.memcheck_enabled, 1, NULL, NULL),
|
||||
createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG, server.use_exit_on_panic, 0, NULL, NULL),
|
||||
createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, server.disable_thp, 1, NULL, NULL),
|
||||
createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, server.cluster_allow_replica_migration, 1, NULL, NULL),
|
||||
createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, server.replica_announced, 1, NULL, NULL),
|
||||
|
||||
/* String Configs */
|
||||
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL),
|
||||
createStringConfig("unixsocket", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.unixsocket, NULL, NULL, NULL),
|
||||
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL),
|
||||
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL),
|
||||
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
|
||||
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
|
||||
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, NULL),
|
||||
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
|
||||
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
|
||||
@@ -2457,7 +2459,8 @@ standardConfig configs[] = {
|
||||
createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate),
|
||||
|
||||
/* SDS Configs */
|
||||
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
|
||||
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
|
||||
createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.requirepass, NULL, NULL, updateRequirePass),
|
||||
|
||||
/* Enum Configs */
|
||||
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),
|
||||
@@ -2467,12 +2470,12 @@ standardConfig configs[] = {
|
||||
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, server.maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
|
||||
createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, server.aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),
|
||||
createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, server.oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
|
||||
createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, server.acl_pubusub_default, USER_FLAG_ALLCHANNELS, NULL, NULL),
|
||||
createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, server.acl_pubsub_default, USER_FLAG_ALLCHANNELS, NULL, NULL),
|
||||
createEnumConfig("sanitize-dump-payload", NULL, MODIFIABLE_CONFIG, sanitize_dump_payload_enum, server.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL),
|
||||
|
||||
/* Integer configs */
|
||||
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
|
||||
createIntConfig("port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, updatePort), /* TCP port. */
|
||||
createIntConfig("io-threads", NULL, IMMUTABLE_CONFIG, 1, 128, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */
|
||||
createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */
|
||||
@@ -2494,6 +2497,7 @@ standardConfig configs[] = {
|
||||
createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
|
||||
createIntConfig("cluster-announce-bus-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, NULL), /* Default: Use +10000 offset. */
|
||||
createIntConfig("cluster-announce-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.port */
|
||||
createIntConfig("cluster-announce-tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.tls_port */
|
||||
createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
@@ -2534,13 +2538,14 @@ standardConfig configs[] = {
|
||||
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
|
||||
createSizeTConfig("client-query-buffer-limit", NULL, MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.client_max_querybuf_len, 1024*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Default: 1GB max query buffer. */
|
||||
|
||||
/* Other configs */
|
||||
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */
|
||||
createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL),
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTlsCfgInt), /* TCP port. */
|
||||
createIntConfig("tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTLSPort), /* TCP port. */
|
||||
createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
||||
createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
||||
createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, updateTlsCfgBool),
|
||||
@@ -2550,8 +2555,10 @@ standardConfig configs[] = {
|
||||
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool),
|
||||
createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg),
|
||||
|
||||
+10
-18
@@ -35,12 +35,16 @@
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/version.h>
|
||||
#include <features.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
|
||||
#define MAC_OS_10_6_DETECTED
|
||||
#endif
|
||||
|
||||
/* Define redis_fstat to fstat or fstat64() */
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_10_6_DETECTED)
|
||||
#define redis_fstat fstat64
|
||||
#define redis_stat stat64
|
||||
#else
|
||||
@@ -79,7 +83,7 @@
|
||||
#define HAVE_EPOLL 1
|
||||
#endif
|
||||
|
||||
#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
|
||||
#if (defined(__APPLE__) && defined(MAC_OS_10_6_DETECTED)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
|
||||
#define HAVE_KQUEUE 1
|
||||
#endif
|
||||
|
||||
@@ -114,19 +118,7 @@
|
||||
|
||||
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
|
||||
* the plain fsync() call. */
|
||||
#ifdef __linux__
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
|
||||
#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6))
|
||||
#define HAVE_SYNC_FILE_RANGE 1
|
||||
#endif
|
||||
#else
|
||||
#if (LINUX_VERSION_CODE >= 0x020611)
|
||||
#define HAVE_SYNC_FILE_RANGE 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYNC_FILE_RANGE
|
||||
#if (defined(__linux__) && defined(SYNC_FILE_RANGE_WAIT_BEFORE))
|
||||
#define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE)
|
||||
#else
|
||||
#define rdb_fsync_range(fd,off,size) fsync(fd)
|
||||
@@ -143,7 +135,7 @@
|
||||
#define ESOCKTNOSUPPORT 0
|
||||
#endif
|
||||
|
||||
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
|
||||
#if (defined __linux || defined __APPLE__)
|
||||
#define USE_SETPROCTITLE
|
||||
#define INIT_SETPROCTITLE_REPLACEMENT
|
||||
void spt_init(int argc, char *argv[]);
|
||||
@@ -260,7 +252,7 @@ void setproctitle(const char *fmt, ...);
|
||||
#include <kernel/OS.h>
|
||||
#define redis_set_thread_title(name) rename_thread(find_thread(0), name)
|
||||
#else
|
||||
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
|
||||
#if (defined __APPLE__ && defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070)
|
||||
int pthread_setname_np(const char *name);
|
||||
#include <pthread.h>
|
||||
#define redis_set_thread_title(name) pthread_setname_np(name)
|
||||
|
||||
+1
-1
@@ -427,7 +427,7 @@ int connGetState(connection *conn) {
|
||||
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
|
||||
*/
|
||||
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
|
||||
snprintf(buf, buf_len-1, "fd=%i", conn->fd);
|
||||
snprintf(buf, buf_len-1, "fd=%i", conn == NULL ? -1 : conn->fd);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -127,9 +127,10 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
#include <stdio.h>
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
int crc64Test(int argc, char *argv[]) {
|
||||
int crc64Test(int argc, char *argv[], int accurate) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(accurate);
|
||||
crc64_init();
|
||||
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
||||
(uint64_t)_crc64(0, "123456789", 9));
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ void crc64_init(void);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int crc64Test(int argc, char *argv[]);
|
||||
int crc64Test(int argc, char *argv[], int accurate);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "atomicvar.h"
|
||||
#include "latency.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <ctype.h>
|
||||
@@ -139,9 +140,9 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
|
||||
keymiss:
|
||||
if (!(flags & LOOKUP_NONOTIFY)) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
}
|
||||
server.stat_keyspace_misses++;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -164,16 +165,24 @@ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||
}
|
||||
|
||||
void SentReplyOnKeyMiss(client *c, robj *reply){
|
||||
serverAssert(sdsEncodedObject(reply));
|
||||
sds rep = reply->ptr;
|
||||
if (sdslen(rep) > 1 && rep[0] == '-'){
|
||||
addReplyErrorObject(c, reply);
|
||||
} else {
|
||||
addReply(c,reply);
|
||||
}
|
||||
}
|
||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
||||
robj *o = lookupKeyRead(c->db, key);
|
||||
if (!o) addReply(c,reply);
|
||||
if (!o) SentReplyOnKeyMiss(c, reply);
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
|
||||
robj *o = lookupKeyWrite(c->db, key);
|
||||
if (!o) addReply(c,reply);
|
||||
if (!o) SentReplyOnKeyMiss(c, reply);
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -568,7 +577,7 @@ long long dbTotalServerKeyCount() {
|
||||
* a context of a client. */
|
||||
void signalModifiedKey(client *c, redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
trackingInvalidateKey(c,key);
|
||||
trackingInvalidateKey(c,key,1);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid, int async) {
|
||||
@@ -1315,7 +1324,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
|
||||
*
|
||||
* Returns C_ERR if at least one of the DB ids are out of range, otherwise
|
||||
* C_OK is returned. */
|
||||
int dbSwapDatabases(long id1, long id2) {
|
||||
int dbSwapDatabases(int id1, int id2) {
|
||||
if (id1 < 0 || id1 >= server.dbnum ||
|
||||
id2 < 0 || id2 >= server.dbnum) return C_ERR;
|
||||
if (id1 == id2) return C_OK;
|
||||
@@ -1356,7 +1365,7 @@ int dbSwapDatabases(long id1, long id2) {
|
||||
|
||||
/* SWAPDB db1 db2 */
|
||||
void swapdbCommand(client *c) {
|
||||
long id1, id2;
|
||||
int id1, id2;
|
||||
|
||||
/* Not allowed in cluster mode: we have just DB 0 there. */
|
||||
if (server.cluster_enabled) {
|
||||
@@ -1365,11 +1374,11 @@ void swapdbCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Get the two DBs indexes. */
|
||||
if (getLongFromObjectOrReply(c, c->argv[1], &id1,
|
||||
if (getIntFromObjectOrReply(c, c->argv[1], &id1,
|
||||
"invalid first DB index") != C_OK)
|
||||
return;
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &id2,
|
||||
if (getIntFromObjectOrReply(c, c->argv[2], &id2,
|
||||
"invalid second DB index") != C_OK)
|
||||
return;
|
||||
|
||||
@@ -1429,6 +1438,22 @@ long long getExpire(redisDb *db, robj *key) {
|
||||
return dictGetSignedIntegerVal(de);
|
||||
}
|
||||
|
||||
/* Delete the specified expired key and propagate expire. */
|
||||
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) {
|
||||
mstime_t expire_latency;
|
||||
latencyStartMonitor(expire_latency);
|
||||
if (server.lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
latencyEndMonitor(expire_latency);
|
||||
latencyAddSampleIfNeeded("expire-del",expire_latency);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",keyobj,db->id);
|
||||
signalModifiedKey(NULL, db, keyobj);
|
||||
propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
|
||||
server.stat_expiredkeys++;
|
||||
}
|
||||
|
||||
/* Propagate expires into slaves and the AOF file.
|
||||
* When a key expires in the master, a DEL operation for this key is sent
|
||||
* to all the slaves and the AOF file if enabled.
|
||||
@@ -1445,7 +1470,12 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
incrRefCount(argv[0]);
|
||||
incrRefCount(argv[1]);
|
||||
|
||||
/* If the master decided to expire a key we must propagate it to replicas no matter what..
|
||||
* Even if module executed a command without asking for propagation. */
|
||||
int prev_replication_allowed = server.replication_allowed;
|
||||
server.replication_allowed = 1;
|
||||
propagate(server.delCommand,db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
server.replication_allowed = prev_replication_allowed;
|
||||
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
@@ -1467,7 +1497,7 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
if (server.lua_caller) {
|
||||
now = server.lua_time_start;
|
||||
now = server.lua_time_snapshot;
|
||||
}
|
||||
/* If we are in the middle of a command execution, we still want to use
|
||||
* a reference time that does not change: in that case we just use the
|
||||
@@ -1528,14 +1558,8 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
if (checkClientPauseTimeoutAndReturnIfPaused()) return 1;
|
||||
|
||||
/* Delete the key */
|
||||
server.stat_expiredkeys++;
|
||||
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(NULL,db,key);
|
||||
return retval;
|
||||
deleteExpiredKeyAndPropagate(db,key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@@ -1599,7 +1623,6 @@ int getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, get
|
||||
* return no keys and expect the command implementation to report
|
||||
* an arity or syntax error. */
|
||||
if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
|
||||
getKeysFreeResult(result);
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
+90
-35
@@ -54,6 +54,10 @@ typedef ucontext_t sigcontext_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__arm64__)
|
||||
#include <mach/mach.h>
|
||||
#endif
|
||||
|
||||
/* Globals */
|
||||
static int bug_report_start = 0; /* True if bug report header was already logged. */
|
||||
static pthread_mutex_t bug_report_start_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -245,7 +249,7 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
RedisModuleDigest md;
|
||||
RedisModuleDigest md = {{0},{0}};
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitDigestContext(md);
|
||||
@@ -437,7 +441,7 @@ void debugCommand(client *c) {
|
||||
" conflicting keys will generate an exception and kill the server."
|
||||
" * NOSAVE: the database will be loaded from an existing RDB file.",
|
||||
" Examples:",
|
||||
" * DEBUG RELOAD: verify that the server is able to persist, flsuh and reload",
|
||||
" * DEBUG RELOAD: verify that the server is able to persist, flush and reload",
|
||||
" the database.",
|
||||
" * DEBUG RELOAD NOSAVE: replace the current database with the contents of an",
|
||||
" existing RDB file.",
|
||||
@@ -463,13 +467,15 @@ void debugCommand(client *c) {
|
||||
" Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key>",
|
||||
" Show low level info about the ziplist encoding of <key>.",
|
||||
"PAUSE-CRON <0|1>",
|
||||
" Stop periodic cron job processing.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"panic")) {
|
||||
serverPanic("DEBUG PANIC called at Unix time %ld", time(NULL));
|
||||
serverPanic("DEBUG PANIC called at Unix time %lld", (long long)time(NULL));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"restart") ||
|
||||
!strcasecmp(c->argv[1]->ptr,"crash-and-recover"))
|
||||
{
|
||||
@@ -717,7 +723,7 @@ NULL
|
||||
} else if (!strcasecmp(name,"double")) {
|
||||
addReplyDouble(c,3.14159265359);
|
||||
} else if (!strcasecmp(name,"bignum")) {
|
||||
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
|
||||
addReplyBigNum(c,"1234567999999999999999999999999999999",37);
|
||||
} else if (!strcasecmp(name,"null")) {
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(name,"array")) {
|
||||
@@ -733,18 +739,27 @@ NULL
|
||||
addReplyBool(c, j == 1);
|
||||
}
|
||||
} else if (!strcasecmp(name,"attrib")) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
if (c->resp >= 3) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
}
|
||||
/* Attributes are not real replies, so a well formed reply should
|
||||
* also have a normal reply type after the attribute. */
|
||||
addReplyBulkCString(c,"Some real reply following the attribute");
|
||||
} else if (!strcasecmp(name,"push")) {
|
||||
if (c->resp < 3) {
|
||||
addReplyError(c,"RESP2 is not supported by this command");
|
||||
return;
|
||||
}
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCString(c,"server-cpu-usage");
|
||||
addReplyLongLong(c,42);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
/* Push replies are not synchronous replies, so we emit also a
|
||||
* normal reply in order for blocking clients just discarding the
|
||||
* push reply, to actually consume the reply and continue. */
|
||||
@@ -881,6 +896,9 @@ NULL
|
||||
mallctl_string(c, c->argv+2, c->argc-2);
|
||||
return;
|
||||
#endif
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"pause-cron") && c->argc == 3) {
|
||||
server.pause_cron = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -1013,61 +1031,75 @@ void bugReportStart(void) {
|
||||
}
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
static void *getMcontextEip(ucontext_t *uc) {
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
|
||||
/* Returns the current eip and set it to the given new value (if its not NULL) */
|
||||
static void* getAndSetMcontextEip(ucontext_t *uc, void *eip) {
|
||||
#define GET_SET_RETURN(target_var, new_val) do {\
|
||||
void *old_val = (void*)target_var; \
|
||||
if (new_val) { \
|
||||
void **temp = (void**)&target_var; \
|
||||
*temp = new_val; \
|
||||
} \
|
||||
return old_val; \
|
||||
} while(0)
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_10_6_DETECTED)
|
||||
/* OSX < 10.6 */
|
||||
#if defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext->__ss.__rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__rip, eip);
|
||||
#elif defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__eip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__eip, eip);
|
||||
#else
|
||||
return (void*) uc->uc_mcontext->__ss.__srr0;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__srr0, eip);
|
||||
#endif
|
||||
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
|
||||
#elif defined(__APPLE__) && defined(MAC_OS_10_6_DETECTED)
|
||||
/* OSX >= 10.6 */
|
||||
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__rip, eip);
|
||||
#elif defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__eip;
|
||||
GET_SET_RETURN(uc->uc_mcontext->__ss.__eip, eip);
|
||||
#else
|
||||
/* OSX ARM64 */
|
||||
return (void*) arm_thread_state64_get_pc(uc->uc_mcontext->__ss);
|
||||
void *old_val = (void*)arm_thread_state64_get_pc(uc->uc_mcontext->__ss);
|
||||
if (eip) {
|
||||
arm_thread_state64_set_pc_fptr(uc->uc_mcontext->__ss, eip);
|
||||
}
|
||||
return old_val;
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
/* Linux */
|
||||
#if defined(__i386__) || ((defined(__X86_64__) || defined(__x86_64__)) && defined(__ILP32__))
|
||||
return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
|
||||
GET_SET_RETURN(uc->uc_mcontext.gregs[14], eip);
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
|
||||
GET_SET_RETURN(uc->uc_mcontext.gregs[16], eip);
|
||||
#elif defined(__ia64__) /* Linux IA64 */
|
||||
return (void*) uc->uc_mcontext.sc_ip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.sc_ip, eip);
|
||||
#elif defined(__arm__) /* Linux ARM */
|
||||
return (void*) uc->uc_mcontext.arm_pc;
|
||||
GET_SET_RETURN(uc->uc_mcontext.arm_pc, eip);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
return (void*) uc->uc_mcontext.pc;
|
||||
GET_SET_RETURN(uc->uc_mcontext.pc, eip);
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
/* FreeBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.mc_eip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.mc_eip, eip);
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.mc_rip, eip);
|
||||
#endif
|
||||
#elif defined(__OpenBSD__)
|
||||
/* OpenBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->sc_eip;
|
||||
GET_SET_RETURN(uc->sc_eip, eip);
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->sc_rip;
|
||||
GET_SET_RETURN(uc->sc_rip, eip);
|
||||
#endif
|
||||
#elif defined(__NetBSD__)
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.__gregs[_REG_EIP];
|
||||
GET_SET_RETURN(uc->uc_mcontext.__gregs[_REG_EIP], eip);
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.__gregs[_REG_RIP];
|
||||
GET_SET_RETURN(uc->uc_mcontext.__gregs[_REG_RIP], eip);
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
GET_SET_RETURN(uc->uc_mcontext.mc_rip, eip);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
@@ -1091,7 +1123,7 @@ void logRegisters(ucontext_t *uc) {
|
||||
serverLog(LL_WARNING|LL_RAW, "\n------ REGISTERS ------\n");
|
||||
|
||||
/* OSX */
|
||||
#if defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
|
||||
#if defined(__APPLE__) && defined(MAC_OS_10_6_DETECTED)
|
||||
/* OSX AMD64 */
|
||||
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
@@ -1794,6 +1826,10 @@ void dumpCodeAroundEIP(void *eip) {
|
||||
}
|
||||
}
|
||||
|
||||
void invalidFunctionWasCalled() {}
|
||||
|
||||
typedef void (*invalidFunctionWasCalledType)();
|
||||
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
UNUSED(secret);
|
||||
UNUSED(info);
|
||||
@@ -1805,19 +1841,36 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
serverLog(LL_WARNING,
|
||||
"Accessing address: %p", (void*)info->si_addr);
|
||||
}
|
||||
if (info->si_pid != -1) {
|
||||
if (info->si_code == SI_USER && info->si_pid != -1) {
|
||||
serverLog(LL_WARNING, "Killed by PID: %ld, UID: %d", (long) info->si_pid, info->si_uid);
|
||||
}
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
ucontext_t *uc = (ucontext_t*) secret;
|
||||
void *eip = getMcontextEip(uc);
|
||||
void *eip = getAndSetMcontextEip(uc, NULL);
|
||||
if (eip != NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Crashed running the instruction at: %p", eip);
|
||||
}
|
||||
|
||||
logStackTrace(getMcontextEip(uc), 1);
|
||||
if (eip == info->si_addr) {
|
||||
/* When eip matches the bad address, it's an indication that we crashed when calling a non-mapped
|
||||
* function pointer. In that case the call to backtrace will crash trying to access that address and we
|
||||
* won't get a crash report logged. Set it to a valid point to avoid that crash. */
|
||||
|
||||
/* This trick allow to avoid compiler warning */
|
||||
void *ptr;
|
||||
invalidFunctionWasCalledType *ptr_ptr = (invalidFunctionWasCalledType*)&ptr;
|
||||
*ptr_ptr = invalidFunctionWasCalled;
|
||||
getAndSetMcontextEip(uc, ptr);
|
||||
}
|
||||
|
||||
logStackTrace(eip, 1);
|
||||
|
||||
if (eip == info->si_addr) {
|
||||
/* Restore old eip */
|
||||
getAndSetMcontextEip(uc, eip);
|
||||
}
|
||||
|
||||
logRegisters(uc);
|
||||
#endif
|
||||
@@ -1853,7 +1906,9 @@ void bugReportEnd(int killViaSignal, int sig) {
|
||||
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
" Please report the crash by opening an issue on github:\n\n"
|
||||
" http://github.com/redis/redis/issues\n\n"
|
||||
" If a Redis module was involved, please open in the module's repo instead.\n\n"
|
||||
" Suspect RAM error? Use redis-server --test-memory to verify it.\n\n"
|
||||
" Some other issues could be detected by redis-server --check-system\n"
|
||||
);
|
||||
|
||||
/* free(messages); Don't call free() with possibly corrupted memory. */
|
||||
@@ -1912,7 +1967,7 @@ void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) {
|
||||
|
||||
serverLogFromHandler(LL_WARNING,"\n--- WATCHDOG TIMER EXPIRED ---");
|
||||
#ifdef HAVE_BACKTRACE
|
||||
logStackTrace(getMcontextEip(uc), 1);
|
||||
logStackTrace(getAndSetMcontextEip(uc, NULL), 1);
|
||||
#else
|
||||
serverLogFromHandler(LL_WARNING,"Sorry: no support for backtrace().");
|
||||
#endif
|
||||
|
||||
+3
-1
@@ -347,7 +347,9 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
if ((newsds = activeDefragSds(sdsele))) {
|
||||
/* When defragging an sds value, we need to update the dict key */
|
||||
uint64_t hash = dictGetHash(d, newsds);
|
||||
replaceSatelliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, sdsele, hash);
|
||||
if (deref)
|
||||
(*deref)->key = newsds;
|
||||
ln->value = newsds;
|
||||
defragged++;
|
||||
}
|
||||
|
||||
+68
-44
@@ -45,21 +45,17 @@
|
||||
|
||||
#include "dict.h"
|
||||
#include "zmalloc.h"
|
||||
#ifndef DICT_BENCHMARK_MAIN
|
||||
#include "redisassert.h"
|
||||
#else
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to
|
||||
* enable/disable resizing of the hash table as needed. This is very important
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to disable
|
||||
* resizing and rehashing of the hash table as needed. This is very important
|
||||
* for Redis, as we use copy-on-write and don't want to move too much memory
|
||||
* around when there is a child performing saving operations.
|
||||
*
|
||||
* Note that even when dict_can_resize is set to 0, not all resizes are
|
||||
* prevented: a hash table is still allowed to grow if the ratio between
|
||||
* the number of elements and the buckets > dict_force_resize_ratio. */
|
||||
static int dict_can_resize = 1;
|
||||
static dictResizeEnable dict_can_resize = DICT_RESIZE_ENABLE;
|
||||
static unsigned int dict_force_resize_ratio = 5;
|
||||
|
||||
/* -------------------------- private prototypes ---------------------------- */
|
||||
@@ -136,7 +132,7 @@ int dictResize(dict *d)
|
||||
{
|
||||
unsigned long minimal;
|
||||
|
||||
if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
|
||||
if (dict_can_resize != DICT_RESIZE_ENABLE || dictIsRehashing(d)) return DICT_ERR;
|
||||
minimal = d->ht[0].used;
|
||||
if (minimal < DICT_HT_INITIAL_SIZE)
|
||||
minimal = DICT_HT_INITIAL_SIZE;
|
||||
@@ -158,6 +154,10 @@ int _dictExpand(dict *d, unsigned long size, int* malloc_failed)
|
||||
dictht n; /* the new hash table */
|
||||
unsigned long realsize = _dictNextPower(size);
|
||||
|
||||
/* Detect overflows */
|
||||
if (realsize < size || realsize * sizeof(dictEntry*) < realsize)
|
||||
return DICT_ERR;
|
||||
|
||||
/* Rehashing to the same table size is not useful. */
|
||||
if (realsize == d->ht[0].size) return DICT_ERR;
|
||||
|
||||
@@ -210,7 +210,15 @@ int dictTryExpand(dict *d, unsigned long size) {
|
||||
* work it does would be unbound and the function may block for a long time. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
int empty_visits = n*10; /* Max number of empty buckets to visit. */
|
||||
if (!dictIsRehashing(d)) return 0;
|
||||
unsigned long s0 = d->ht[0].size;
|
||||
unsigned long s1 = d->ht[1].size;
|
||||
if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0;
|
||||
if (dict_can_resize == DICT_RESIZE_AVOID &&
|
||||
((s1 > s0 && s1 / s0 < dict_force_resize_ratio) ||
|
||||
(s1 < s0 && s0 / s1 < dict_force_resize_ratio)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while(n-- && d->ht[0].used != 0) {
|
||||
dictEntry *de, *nextde;
|
||||
@@ -534,8 +542,8 @@ void *dictFetchValue(dict *d, const void *key) {
|
||||
* the fingerprint again when the iterator is released.
|
||||
* If the two fingerprints are different it means that the user of the iterator
|
||||
* performed forbidden operations against the dictionary while iterating. */
|
||||
long long dictFingerprint(dict *d) {
|
||||
long long integers[6], hash = 0;
|
||||
unsigned long long dictFingerprint(dict *d) {
|
||||
unsigned long long integers[6], hash = 0;
|
||||
int j;
|
||||
|
||||
integers[0] = (long) d->ht[0].table;
|
||||
@@ -995,10 +1003,12 @@ static int _dictExpandIfNeeded(dict *d)
|
||||
* table (global setting) or we should avoid it but the ratio between
|
||||
* elements/buckets is over the "safe" threshold, we resize doubling
|
||||
* the number of buckets. */
|
||||
if (d->ht[0].used >= d->ht[0].size &&
|
||||
(dict_can_resize ||
|
||||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio) &&
|
||||
dictTypeExpandAllowed(d))
|
||||
if (!dictTypeExpandAllowed(d))
|
||||
return DICT_OK;
|
||||
if ((dict_can_resize == DICT_RESIZE_ENABLE &&
|
||||
d->ht[0].used >= d->ht[0].size) ||
|
||||
(dict_can_resize != DICT_RESIZE_FORBID &&
|
||||
d->ht[0].used / d->ht[0].size > dict_force_resize_ratio))
|
||||
{
|
||||
return dictExpand(d, d->ht[0].used + 1);
|
||||
}
|
||||
@@ -1057,12 +1067,8 @@ void dictEmpty(dict *d, void(callback)(void*)) {
|
||||
d->pauserehash = 0;
|
||||
}
|
||||
|
||||
void dictEnableResize(void) {
|
||||
dict_can_resize = 1;
|
||||
}
|
||||
|
||||
void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
void dictSetResizeEnabled(dictResizeEnable enable) {
|
||||
dict_can_resize = enable;
|
||||
}
|
||||
|
||||
uint64_t dictGetHash(dict *d, const void *key) {
|
||||
@@ -1105,7 +1111,9 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {
|
||||
|
||||
if (ht->used == 0) {
|
||||
return snprintf(buf,bufsize,
|
||||
"No stats available for empty dictionaries\n");
|
||||
"Hash table %d stats (%s):\n"
|
||||
"No stats available for empty dictionaries\n",
|
||||
tableid, (tableid == 0) ? "main hash table" : "rehashing target");
|
||||
}
|
||||
|
||||
/* Compute stats. */
|
||||
@@ -1175,20 +1183,18 @@ void dictGetStats(char *buf, size_t bufsize, dict *d) {
|
||||
|
||||
/* ------------------------------- Benchmark ---------------------------------*/
|
||||
|
||||
#ifdef DICT_BENCHMARK_MAIN
|
||||
|
||||
#include "sds.h"
|
||||
#ifdef REDIS_TEST
|
||||
|
||||
uint64_t hashCallback(const void *key) {
|
||||
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
|
||||
return dictGenHashFunction((unsigned char*)key, strlen((char*)key));
|
||||
}
|
||||
|
||||
int compareCallback(void *privdata, const void *key1, const void *key2) {
|
||||
int l1,l2;
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
l1 = sdslen((sds)key1);
|
||||
l2 = sdslen((sds)key2);
|
||||
l1 = strlen((char*)key1);
|
||||
l2 = strlen((char*)key2);
|
||||
if (l1 != l2) return 0;
|
||||
return memcmp(key1, key2, l1) == 0;
|
||||
}
|
||||
@@ -1196,7 +1202,19 @@ int compareCallback(void *privdata, const void *key1, const void *key2) {
|
||||
void freeCallback(void *privdata, void *val) {
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
sdsfree(val);
|
||||
zfree(val);
|
||||
}
|
||||
|
||||
char *stringFromLongLong(long long value) {
|
||||
char buf[32];
|
||||
int len;
|
||||
char *s;
|
||||
|
||||
len = sprintf(buf,"%lld",value);
|
||||
s = zmalloc(len+1);
|
||||
memcpy(s, buf, len);
|
||||
s[len] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
dictType BenchmarkDictType = {
|
||||
@@ -1215,22 +1233,26 @@ dictType BenchmarkDictType = {
|
||||
printf(msg ": %ld items in %lld ms\n", count, elapsed); \
|
||||
} while(0)
|
||||
|
||||
/* dict-benchmark [count] */
|
||||
int main(int argc, char **argv) {
|
||||
/* ./redis-server test dict [<count> | --accurate] */
|
||||
int dictTest(int argc, char **argv, int accurate) {
|
||||
long j;
|
||||
long long start, elapsed;
|
||||
dict *dict = dictCreate(&BenchmarkDictType,NULL);
|
||||
long count = 0;
|
||||
|
||||
if (argc == 2) {
|
||||
count = strtol(argv[1],NULL,10);
|
||||
if (argc == 4) {
|
||||
if (accurate) {
|
||||
count = 5000000;
|
||||
} else {
|
||||
count = strtol(argv[3],NULL,10);
|
||||
}
|
||||
} else {
|
||||
count = 5000000;
|
||||
count = 5000;
|
||||
}
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
int retval = dictAdd(dict,sdsfromlonglong(j),(void*)j);
|
||||
int retval = dictAdd(dict,stringFromLongLong(j),(void*)j);
|
||||
assert(retval == DICT_OK);
|
||||
}
|
||||
end_benchmark("Inserting");
|
||||
@@ -1243,28 +1265,28 @@ int main(int argc, char **argv) {
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
sds key = sdsfromlonglong(j);
|
||||
char *key = stringFromLongLong(j);
|
||||
dictEntry *de = dictFind(dict,key);
|
||||
assert(de != NULL);
|
||||
sdsfree(key);
|
||||
zfree(key);
|
||||
}
|
||||
end_benchmark("Linear access of existing elements");
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
sds key = sdsfromlonglong(j);
|
||||
char *key = stringFromLongLong(j);
|
||||
dictEntry *de = dictFind(dict,key);
|
||||
assert(de != NULL);
|
||||
sdsfree(key);
|
||||
zfree(key);
|
||||
}
|
||||
end_benchmark("Linear access of existing elements (2nd round)");
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
sds key = sdsfromlonglong(rand() % count);
|
||||
char *key = stringFromLongLong(rand() % count);
|
||||
dictEntry *de = dictFind(dict,key);
|
||||
assert(de != NULL);
|
||||
sdsfree(key);
|
||||
zfree(key);
|
||||
}
|
||||
end_benchmark("Random access of existing elements");
|
||||
|
||||
@@ -1277,17 +1299,17 @@ int main(int argc, char **argv) {
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
sds key = sdsfromlonglong(rand() % count);
|
||||
char *key = stringFromLongLong(rand() % count);
|
||||
key[0] = 'X';
|
||||
dictEntry *de = dictFind(dict,key);
|
||||
assert(de == NULL);
|
||||
sdsfree(key);
|
||||
zfree(key);
|
||||
}
|
||||
end_benchmark("Accessing missing");
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
sds key = sdsfromlonglong(j);
|
||||
char *key = stringFromLongLong(j);
|
||||
int retval = dictDelete(dict,key);
|
||||
assert(retval == DICT_OK);
|
||||
key[0] += 17; /* Change first number to letter. */
|
||||
@@ -1295,5 +1317,7 @@ int main(int argc, char **argv) {
|
||||
assert(retval == DICT_OK);
|
||||
}
|
||||
end_benchmark("Removing and adding");
|
||||
dictRelease(dict);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
+12
-3
@@ -95,7 +95,7 @@ typedef struct dictIterator {
|
||||
int table, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
/* unsafe iterator fingerprint for misuse detection. */
|
||||
long long fingerprint;
|
||||
unsigned long long fingerprint;
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
@@ -160,6 +160,12 @@ typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
|
||||
#define randomULong() random()
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
DICT_RESIZE_ENABLE,
|
||||
DICT_RESIZE_AVOID,
|
||||
DICT_RESIZE_FORBID,
|
||||
} dictResizeEnable;
|
||||
|
||||
/* API */
|
||||
dict *dictCreate(dictType *type, void *privDataPtr);
|
||||
int dictExpand(dict *d, unsigned long size);
|
||||
@@ -186,8 +192,7 @@ void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
uint64_t dictGenHashFunction(const void *key, int len);
|
||||
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d, void(callback)(void*));
|
||||
void dictEnableResize(void);
|
||||
void dictDisableResize(void);
|
||||
void dictSetResizeEnabled(dictResizeEnable enable);
|
||||
int dictRehash(dict *d, int n);
|
||||
int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
@@ -201,4 +206,8 @@ extern dictType dictTypeHeapStringCopyKey;
|
||||
extern dictType dictTypeHeapStrings;
|
||||
extern dictType dictTypeHeapStringCopyKeyValue;
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int dictTest(int argc, char *argv[], int accurate);
|
||||
#endif
|
||||
|
||||
#endif /* __DICT_H */
|
||||
|
||||
+2
-1
@@ -105,11 +105,12 @@ uint64_t intrev64(uint64_t v) {
|
||||
#include <stdio.h>
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
int endianconvTest(int argc, char *argv[]) {
|
||||
int endianconvTest(int argc, char *argv[], int accurate) {
|
||||
char buf[32];
|
||||
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(accurate);
|
||||
|
||||
sprintf(buf,"ciaoroma");
|
||||
memrev16(buf);
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ uint64_t intrev64(uint64_t v);
|
||||
#endif
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int endianconvTest(int argc, char *argv[]);
|
||||
int endianconvTest(int argc, char *argv[], int accurate);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+9
-3
@@ -549,7 +549,7 @@ int performEvictions(void) {
|
||||
{
|
||||
struct evictionPoolEntry *pool = EvictionPoolLRU;
|
||||
|
||||
while(bestkey == NULL) {
|
||||
while (bestkey == NULL) {
|
||||
unsigned long total_keys = 0, keys;
|
||||
|
||||
/* We don't want to make local-db choices when expiring keys,
|
||||
@@ -695,12 +695,18 @@ cant_free:
|
||||
/* At this point, we have run out of evictable items. It's possible
|
||||
* that some items are being freed in the lazyfree thread. Perform a
|
||||
* short wait here if such jobs exist, but don't wait long. */
|
||||
if (bioPendingJobsOfType(BIO_LAZY_FREE)) {
|
||||
usleep(eviction_time_limit_us);
|
||||
mstime_t lazyfree_latency;
|
||||
latencyStartMonitor(lazyfree_latency);
|
||||
while (bioPendingJobsOfType(BIO_LAZY_FREE) &&
|
||||
elapsedUs(evictionTimer) < eviction_time_limit_us) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
|
||||
result = EVICT_OK;
|
||||
break;
|
||||
}
|
||||
usleep(eviction_time_limit_us < 1000 ? eviction_time_limit_us : 1000);
|
||||
}
|
||||
latencyEndMonitor(lazyfree_latency);
|
||||
latencyAddSampleIfNeeded("eviction-lazyfree",lazyfree_latency);
|
||||
}
|
||||
|
||||
latencyEndMonitor(latency);
|
||||
|
||||
+15
-20
@@ -53,24 +53,11 @@
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
long long t = dictGetSignedIntegerVal(de);
|
||||
mstime_t expire_latency;
|
||||
if (now > t) {
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
|
||||
latencyStartMonitor(expire_latency);
|
||||
if (server.lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
latencyEndMonitor(expire_latency);
|
||||
latencyAddSampleIfNeeded("expire-del",expire_latency);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
signalModifiedKey(NULL, db, keyobj);
|
||||
deleteExpiredKeyAndPropagate(db,keyobj);
|
||||
decrRefCount(keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -506,15 +493,23 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
|
||||
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
|
||||
return;
|
||||
int negative_when = when < 0;
|
||||
if (unit == UNIT_SECONDS) when *= 1000;
|
||||
when += basetime;
|
||||
if (((when < 0) && !negative_when) || ((when-basetime > 0) && negative_when)) {
|
||||
/* EXPIRE allows negative numbers, but we can at least detect an
|
||||
* overflow by either unit conversion or basetime addition. */
|
||||
|
||||
/* EXPIRE allows negative numbers, but we can at least detect an
|
||||
* overflow by either unit conversion or basetime addition. */
|
||||
if (unit == UNIT_SECONDS) {
|
||||
if (when > LLONG_MAX / 1000 || when < LLONG_MIN / 1000) {
|
||||
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
|
||||
return;
|
||||
}
|
||||
when *= 1000;
|
||||
}
|
||||
|
||||
if (when > LLONG_MAX - basetime) {
|
||||
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
|
||||
return;
|
||||
}
|
||||
when += basetime;
|
||||
|
||||
/* No key, return zero. */
|
||||
if (lookupKeyWrite(c->db,key) == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
|
||||
#ifdef __linux__
|
||||
/* features.h uses the defines above to set feature specific defines. */
|
||||
#include <linux/version.h>
|
||||
#include <features.h>
|
||||
#endif
|
||||
|
||||
|
||||
@@ -509,7 +509,7 @@ void geoaddCommand(client *c) {
|
||||
* [COUNT count [ANY]] [STORE key] [STOREDIST key]
|
||||
* GEORADIUSBYMEMBER key member radius unit ... options ...
|
||||
* GEOSEARCH key [FROMMEMBER member] [FROMLONLAT long lat] [BYRADIUS radius unit]
|
||||
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC]
|
||||
* [BYBOX width height unit] [WITHCOORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC]
|
||||
* GEOSEARCHSTORE dest_key src_key [FROMMEMBER member] [FROMLONLAT long lat] [BYRADIUS radius unit]
|
||||
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC] [STOREDIST]
|
||||
* */
|
||||
@@ -518,21 +518,24 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[srcKeyIndex], shared.emptyarray)) == NULL ||
|
||||
checkType(c, zobj, OBJ_ZSET)) {
|
||||
return;
|
||||
}
|
||||
robj *zobj = lookupKeyRead(c->db, c->argv[srcKeyIndex]);
|
||||
if (checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Find long/lat to use for radius or box search based on inquiry type */
|
||||
int base_args;
|
||||
GeoShape shape = {0};
|
||||
if (flags & RADIUS_COORDS) {
|
||||
/* GEORADIUS or GEORADIUS_RO */
|
||||
base_args = 6;
|
||||
shape.type = CIRCULAR_TYPE;
|
||||
if (extractLongLatOrReply(c, c->argv + 2, shape.xy) == C_ERR) return;
|
||||
if (extractDistanceOrReply(c, c->argv+base_args-2, &shape.conversion, &shape.t.radius) != C_OK) return;
|
||||
} else if ((flags & RADIUS_MEMBER) && !zobj) {
|
||||
/* We don't have a source key, but we need to proceed with argument
|
||||
* parsing, so we know which reply to use depending on the STORE flag. */
|
||||
base_args = 5;
|
||||
} else if (flags & RADIUS_MEMBER) {
|
||||
/* GEORADIUSBYMEMBER or GEORADIUSBYMEMBER_RO */
|
||||
base_args = 5;
|
||||
shape.type = CIRCULAR_TYPE;
|
||||
robj *member = c->argv[2];
|
||||
@@ -542,6 +545,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
}
|
||||
if (extractDistanceOrReply(c, c->argv+base_args-2, &shape.conversion, &shape.t.radius) != C_OK) return;
|
||||
} else if (flags & GEOSEARCH) {
|
||||
/* GEOSEARCH or GEOSEARCHSTORE */
|
||||
base_args = 2;
|
||||
if (flags & GEOSEARCHSTORE) {
|
||||
base_args = 3;
|
||||
@@ -608,6 +612,13 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
flags & GEOSEARCH &&
|
||||
!fromloc)
|
||||
{
|
||||
/* No source key, proceed with argument parsing and return an error when done. */
|
||||
if (zobj == NULL) {
|
||||
frommember = 1;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (longLatFromMember(zobj, c->argv[base_args+i+1], shape.xy) == C_ERR) {
|
||||
addReplyError(c, "could not decode requested zset member");
|
||||
return;
|
||||
@@ -676,6 +687,23 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return ASAP when src key does not exist. */
|
||||
if (zobj == NULL) {
|
||||
if (storekey) {
|
||||
/* store key is not NULL, try to delete it and return 0. */
|
||||
if (dbDelete(c->db, storekey)) {
|
||||
signalModifiedKey(c, c->db, storekey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", storekey, c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c, shared.czero);
|
||||
} else {
|
||||
/* Otherwise we return an empty array. */
|
||||
addReply(c, shared.emptyarray);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* COUNT without ordering does not make much sense (we need to
|
||||
* sort in order to return the closest N entries),
|
||||
* force ASC ordering if COUNT was specified but no sorting was
|
||||
@@ -770,7 +798,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
robj *zobj;
|
||||
zset *zs;
|
||||
int i;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
|
||||
if (returned_items) {
|
||||
zobj = createZsetObject();
|
||||
@@ -785,13 +813,14 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||
size_t elelen = sdslen(gp->member);
|
||||
|
||||
if (maxelelen < elelen) maxelelen = elelen;
|
||||
totelelen += elelen;
|
||||
znode = zslInsert(zs->zsl,score,gp->member);
|
||||
serverAssert(dictAdd(zs->dict,gp->member,&znode->score) == DICT_OK);
|
||||
gp->member = NULL;
|
||||
}
|
||||
|
||||
if (returned_items) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen,totelelen);
|
||||
setKey(c,c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,flags & GEOSEARCH ? "geosearchstore" : "georadiusstore",storekey,
|
||||
|
||||
+10
-10
@@ -91,8 +91,8 @@ uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
* \-----------------/ -------- \-----------------/
|
||||
* \ / / \ \ /
|
||||
* \ (long,lat) / / (long,lat) \ \ (long,lat) /
|
||||
* \ / / \ / \
|
||||
* --------- /----------------\ /--------------\
|
||||
* \ / / \ / \
|
||||
* --------- /----------------\ /---------------\
|
||||
* Northern Hemisphere Southern Hemisphere Around the equator
|
||||
*/
|
||||
int geohashBoundingBox(GeoShape *shape, double *bounds) {
|
||||
@@ -164,14 +164,14 @@ GeoHashRadius geohashCalculateAreasByShapeWGS84(GeoShape *shape) {
|
||||
geohashDecode(long_range, lat_range, neighbors.east, &east);
|
||||
geohashDecode(long_range, lat_range, neighbors.west, &west);
|
||||
|
||||
if (geohashGetDistance(longitude,latitude,longitude,north.latitude.max)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,longitude,south.latitude.min)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,east.longitude.max,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (geohashGetDistance(longitude,latitude,west.longitude.min,latitude)
|
||||
< radius_meters) decrease_step = 1;
|
||||
if (north.latitude.max < max_lat)
|
||||
decrease_step = 1;
|
||||
if (south.latitude.min > min_lat)
|
||||
decrease_step = 1;
|
||||
if (east.longitude.max < max_lon)
|
||||
decrease_step = 1;
|
||||
if (west.longitude.min > min_lon)
|
||||
decrease_step = 1;
|
||||
}
|
||||
|
||||
if (steps > 1 && decrease_step) {
|
||||
|
||||
+1
-1
@@ -184,7 +184,7 @@ struct commandHelp {
|
||||
8,
|
||||
"6.2.0" },
|
||||
{ "CLIENT KILL",
|
||||
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [SKIPME yes/no]",
|
||||
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [LADDR ip:port] [SKIPME yes/no]",
|
||||
"Kill the connection of a client",
|
||||
8,
|
||||
"2.4.0" },
|
||||
|
||||
+16
-4
@@ -104,7 +104,8 @@ intset *intsetNew(void) {
|
||||
|
||||
/* Resize the intset */
|
||||
static intset *intsetResize(intset *is, uint32_t len) {
|
||||
uint32_t size = len*intrev32ifbe(is->encoding);
|
||||
uint64_t size = (uint64_t)len*intrev32ifbe(is->encoding);
|
||||
assert(size <= SIZE_MAX - sizeof(intset));
|
||||
is = zrealloc(is,sizeof(intset)+size);
|
||||
return is;
|
||||
}
|
||||
@@ -281,10 +282,10 @@ uint32_t intsetLen(const intset *is) {
|
||||
|
||||
/* Return intset blob size in bytes. */
|
||||
size_t intsetBlobLen(intset *is) {
|
||||
return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
|
||||
return sizeof(intset)+(size_t)intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
|
||||
}
|
||||
|
||||
/* Validate the integrity of the data stracture.
|
||||
/* Validate the integrity of the data structure.
|
||||
* when `deep` is 0, only the integrity of the header is validated.
|
||||
* when `deep` is 1, we make sure there are no duplicate or out of order records. */
|
||||
int intsetValidateIntegrity(const unsigned char *p, size_t size, int deep) {
|
||||
@@ -392,7 +393,7 @@ static void checkConsistency(intset *is) {
|
||||
}
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
int intsetTest(int argc, char **argv) {
|
||||
int intsetTest(int argc, char **argv, int accurate) {
|
||||
uint8_t success;
|
||||
int i;
|
||||
intset *is;
|
||||
@@ -400,6 +401,7 @@ int intsetTest(int argc, char **argv) {
|
||||
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(accurate);
|
||||
|
||||
printf("Value encodings: "); {
|
||||
assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
|
||||
@@ -424,6 +426,7 @@ int intsetTest(int argc, char **argv) {
|
||||
is = intsetAdd(is,4,&success); assert(success);
|
||||
is = intsetAdd(is,4,&success); assert(!success);
|
||||
ok();
|
||||
zfree(is);
|
||||
}
|
||||
|
||||
printf("Large number of random adds: "); {
|
||||
@@ -436,6 +439,7 @@ int intsetTest(int argc, char **argv) {
|
||||
assert(intrev32ifbe(is->length) == inserts);
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
zfree(is);
|
||||
}
|
||||
|
||||
printf("Upgrade from int16 to int32: "); {
|
||||
@@ -447,6 +451,7 @@ int intsetTest(int argc, char **argv) {
|
||||
assert(intsetFind(is,32));
|
||||
assert(intsetFind(is,65535));
|
||||
checkConsistency(is);
|
||||
zfree(is);
|
||||
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,32,NULL);
|
||||
@@ -457,6 +462,7 @@ int intsetTest(int argc, char **argv) {
|
||||
assert(intsetFind(is,-65535));
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
zfree(is);
|
||||
}
|
||||
|
||||
printf("Upgrade from int16 to int64: "); {
|
||||
@@ -468,6 +474,7 @@ int intsetTest(int argc, char **argv) {
|
||||
assert(intsetFind(is,32));
|
||||
assert(intsetFind(is,4294967295));
|
||||
checkConsistency(is);
|
||||
zfree(is);
|
||||
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,32,NULL);
|
||||
@@ -478,6 +485,7 @@ int intsetTest(int argc, char **argv) {
|
||||
assert(intsetFind(is,-4294967295));
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
zfree(is);
|
||||
}
|
||||
|
||||
printf("Upgrade from int32 to int64: "); {
|
||||
@@ -489,6 +497,7 @@ int intsetTest(int argc, char **argv) {
|
||||
assert(intsetFind(is,65535));
|
||||
assert(intsetFind(is,4294967295));
|
||||
checkConsistency(is);
|
||||
zfree(is);
|
||||
|
||||
is = intsetNew();
|
||||
is = intsetAdd(is,65535,NULL);
|
||||
@@ -499,6 +508,7 @@ int intsetTest(int argc, char **argv) {
|
||||
assert(intsetFind(is,-4294967295));
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
zfree(is);
|
||||
}
|
||||
|
||||
printf("Stress lookups: "); {
|
||||
@@ -512,6 +522,7 @@ int intsetTest(int argc, char **argv) {
|
||||
for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL);
|
||||
printf("%ld lookups, %ld element set, %lldusec\n",
|
||||
num,size,usec()-start);
|
||||
zfree(is);
|
||||
}
|
||||
|
||||
printf("Stress add+delete: "); {
|
||||
@@ -528,6 +539,7 @@ int intsetTest(int argc, char **argv) {
|
||||
}
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
zfree(is);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ size_t intsetBlobLen(intset *is);
|
||||
int intsetValidateIntegrity(const unsigned char *is, size_t size, int deep);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int intsetTest(int argc, char *argv[]);
|
||||
int intsetTest(int argc, char *argv[], int accurate);
|
||||
#endif
|
||||
|
||||
#endif // __INTSET_H
|
||||
|
||||
+2
-2
@@ -256,7 +256,7 @@ sds createLatencyReport(void) {
|
||||
if (dictSize(server.latency_events) == 0 &&
|
||||
server.latency_monitor_threshold == 0)
|
||||
{
|
||||
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this Redis instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at http://redis.io/topics/latency-monitor.\n");
|
||||
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this Redis instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at https://redis.io/topics/latency-monitor.\n");
|
||||
return report;
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ sds createLatencyReport(void) {
|
||||
}
|
||||
|
||||
if (advise_slowlog_inspect) {
|
||||
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check http://redis.io/commands/slowlog for more information.\n");
|
||||
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check https://redis.io/commands/slowlog for more information.\n");
|
||||
}
|
||||
|
||||
/* Intrinsic latency. */
|
||||
|
||||
+3
-4
@@ -39,12 +39,11 @@ void lazyfreeFreeSlotsMap(void *args[]) {
|
||||
atomicIncr(lazyfreed_objects,len);
|
||||
}
|
||||
|
||||
/* Release the rax mapping Redis Cluster keys to slots in the
|
||||
* lazyfree thread. */
|
||||
/* Release the key tracking table. */
|
||||
void lazyFreeTrackingTable(void *args[]) {
|
||||
rax *rt = args[0];
|
||||
size_t len = rt->numele;
|
||||
raxFree(rt);
|
||||
freeTrackingRadixTree(rt);
|
||||
atomicDecr(lazyfree_objects,len);
|
||||
atomicIncr(lazyfreed_objects,len);
|
||||
}
|
||||
@@ -110,7 +109,7 @@ size_t lazyfreeGetFreeEffort(robj *key, robj *obj) {
|
||||
/* Every consumer group is an allocation and so are the entries in its
|
||||
* PEL. We use size of the first group's PEL as an estimate for all
|
||||
* others. */
|
||||
if (s->cgroups) {
|
||||
if (s->cgroups && raxSize(s->cgroups)) {
|
||||
raxIterator ri;
|
||||
streamCG *cg;
|
||||
raxStart(&ri,s->cgroups);
|
||||
|
||||
+32
-9
@@ -131,6 +131,8 @@
|
||||
assert((p) >= (lp)+LP_HDR_SIZE && (p)+(len) < (lp)+lpGetTotalBytes((lp))); \
|
||||
} while (0)
|
||||
|
||||
static inline void lpAssertValidEntry(unsigned char* lp, size_t lpbytes, unsigned char *p);
|
||||
|
||||
/* Convert a string into a signed 64 bit integer.
|
||||
* The function returns 1 if the string could be parsed into a (non-overflowing)
|
||||
* signed 64 bit int, 0 otherwise. The 'value' will be set to the parsed value
|
||||
@@ -313,7 +315,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
|
||||
} else {
|
||||
if (size < 64) *enclen = 1+size;
|
||||
else if (size < 4096) *enclen = 2+size;
|
||||
else *enclen = 5+size;
|
||||
else *enclen = 5+(uint64_t)size;
|
||||
return LP_ENCODING_STRING;
|
||||
}
|
||||
}
|
||||
@@ -453,8 +455,8 @@ unsigned char *lpSkip(unsigned char *p) {
|
||||
unsigned char *lpNext(unsigned char *lp, unsigned char *p) {
|
||||
assert(p);
|
||||
p = lpSkip(p);
|
||||
ASSERT_INTEGRITY(lp, p);
|
||||
if (p[0] == LP_EOF) return NULL;
|
||||
lpAssertValidEntry(lp, lpBytes(lp), p);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -468,16 +470,17 @@ unsigned char *lpPrev(unsigned char *lp, unsigned char *p) {
|
||||
uint64_t prevlen = lpDecodeBacklen(p);
|
||||
prevlen += lpEncodeBacklen(NULL,prevlen);
|
||||
p -= prevlen-1; /* Seek the first byte of the previous entry. */
|
||||
ASSERT_INTEGRITY(lp, p);
|
||||
lpAssertValidEntry(lp, lpBytes(lp), p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Return a pointer to the first element of the listpack, or NULL if the
|
||||
* listpack has no elements. */
|
||||
unsigned char *lpFirst(unsigned char *lp) {
|
||||
lp += LP_HDR_SIZE; /* Skip the header. */
|
||||
if (lp[0] == LP_EOF) return NULL;
|
||||
return lp;
|
||||
unsigned char *p = lp + LP_HDR_SIZE; /* Skip the header. */
|
||||
if (p[0] == LP_EOF) return NULL;
|
||||
lpAssertValidEntry(lp, lpBytes(lp), p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Return a pointer to the last element of the listpack, or NULL if the
|
||||
@@ -861,6 +864,13 @@ unsigned char *lpSeek(unsigned char *lp, long index) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Same as lpFirst but without validation assert, to be used right before lpValidateNext. */
|
||||
unsigned char *lpValidateFirst(unsigned char *lp) {
|
||||
unsigned char *p = lp + LP_HDR_SIZE; /* Skip the header. */
|
||||
if (p[0] == LP_EOF) return NULL;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Validate the integrity of a single listpack entry and move to the next one.
|
||||
* The input argument 'pp' is a reference to the current record and is advanced on exit.
|
||||
* Returns 1 if valid, 0 if invalid. */
|
||||
@@ -872,6 +882,10 @@ int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
/* Before accessing p, make sure it's valid. */
|
||||
if (OUT_OF_RANGE(p))
|
||||
return 0;
|
||||
|
||||
if (*p == LP_EOF) {
|
||||
*pp = NULL;
|
||||
return 1;
|
||||
@@ -908,7 +922,12 @@ int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
|
||||
#undef OUT_OF_RANGE
|
||||
}
|
||||
|
||||
/* Validate the integrity of the data stracture.
|
||||
/* Validate that the entry doesn't reach outside the listpack allocation. */
|
||||
static inline void lpAssertValidEntry(unsigned char* lp, size_t lpbytes, unsigned char *p) {
|
||||
assert(lpValidateNext(lp, &p, lpbytes));
|
||||
}
|
||||
|
||||
/* Validate the integrity of the data structure.
|
||||
* when `deep` is 0, only the integrity of the header is validated.
|
||||
* when `deep` is 1, we scan all the entries one by one. */
|
||||
int lpValidateIntegrity(unsigned char *lp, size_t size, int deep){
|
||||
@@ -930,13 +949,17 @@ int lpValidateIntegrity(unsigned char *lp, size_t size, int deep){
|
||||
|
||||
/* Validate the invividual entries. */
|
||||
uint32_t count = 0;
|
||||
unsigned char *p = lpFirst(lp);
|
||||
while(p) {
|
||||
unsigned char *p = lp + LP_HDR_SIZE;
|
||||
while(p && p[0] != LP_EOF) {
|
||||
if (!lpValidateNext(lp, &p, bytes))
|
||||
return 0;
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Make sure 'p' really does point to the end of the listpack. */
|
||||
if (p != lp + size - 1)
|
||||
return 0;
|
||||
|
||||
/* Check that the count in the header is correct */
|
||||
uint32_t numele = lpGetNumElements(lp);
|
||||
if (numele != LP_HDR_NUMELE_UNKNOWN && numele != count)
|
||||
|
||||
@@ -60,6 +60,7 @@ unsigned char *lpPrev(unsigned char *lp, unsigned char *p);
|
||||
uint32_t lpBytes(unsigned char *lp);
|
||||
unsigned char *lpSeek(unsigned char *lp, long index);
|
||||
int lpValidateIntegrity(unsigned char *lp, size_t size, int deep);
|
||||
unsigned char *lpValidateFirst(unsigned char *lp);
|
||||
int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes);
|
||||
|
||||
#endif
|
||||
|
||||
+2
-2
@@ -94,8 +94,8 @@ lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
|
||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
canvas->pixels = zmalloc(width*height);
|
||||
memset(canvas->pixels,bgcolor,width*height);
|
||||
canvas->pixels = zmalloc((size_t)width*height);
|
||||
memset(canvas->pixels,bgcolor,(size_t)width*height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -84,9 +84,9 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
||||
* rows. */
|
||||
float angle = 0;
|
||||
if (y > 1) {
|
||||
float r1 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
float r2 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
float r3 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
float r1 = (float)rand() / (float) RAND_MAX / squares_per_col * y;
|
||||
float r2 = (float)rand() / (float) RAND_MAX / squares_per_col * y;
|
||||
float r3 = (float)rand() / (float) RAND_MAX / squares_per_col * y;
|
||||
if (rand() % 2) r1 = -r1;
|
||||
if (rand() % 2) r2 = -r2;
|
||||
if (rand() % 2) r3 = -r3;
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ void memtest_progress_start(char *title, int pass) {
|
||||
printf("\x1b[H\x1b[2K"); /* Cursor home, clear current line. */
|
||||
printf("%s [%d]\n", title, pass); /* Print title. */
|
||||
progress_printed = 0;
|
||||
progress_full = ws.ws_col*(ws.ws_row-3);
|
||||
progress_full = (size_t)ws.ws_col*(ws.ws_row-3);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
||||
+348
-116
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so
|
||||
all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@@ -58,10 +58,5 @@ helloacl.xo: ../redismodule.h
|
||||
helloacl.so: helloacl.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
testmodule.so: testmodule.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
clean:
|
||||
rm -rf *.xo *.so
|
||||
|
||||
+123
-11
@@ -1,3 +1,4 @@
|
||||
# coding: utf-8
|
||||
# gendoc.rb -- Converts the top-comments inside module.c to modules API
|
||||
# reference documentation in markdown format.
|
||||
|
||||
@@ -21,15 +22,48 @@ def markdown(s)
|
||||
l = l.gsub(/(?<![`A-z])[a-z_]+\(\)/, '`\0`')
|
||||
# Add backquotes around macro and var names containing underscores.
|
||||
l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"}
|
||||
# Link URLs preceded by space (i.e. when not already linked)
|
||||
l = l.gsub(/ (https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/,
|
||||
' [\1](\1)')
|
||||
# Link URLs preceded by space or newline (not already linked)
|
||||
l = l.gsub(/(^| )(https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/,
|
||||
'\1[\2](\2)')
|
||||
# Replace double-dash with unicode ndash
|
||||
l = l.gsub(/ -- /, ' – ')
|
||||
end
|
||||
# Link function names to their definition within the page
|
||||
l = l.gsub(/`(RedisModule_[A-z0-9]+)[()]*`/) {|x|
|
||||
$index[$1] ? "[#{x}](\##{$1})" : x
|
||||
}
|
||||
newlines << l
|
||||
}
|
||||
return newlines.join("\n")
|
||||
end
|
||||
|
||||
# Linebreak a prototype longer than 80 characters on the commas, but only
|
||||
# between balanced parentheses so that we don't linebreak args which are
|
||||
# function pointers, and then aligning each arg under each other.
|
||||
def linebreak_proto(proto, indent)
|
||||
if proto.bytesize <= 80
|
||||
return proto
|
||||
end
|
||||
parts = proto.split(/,\s*/);
|
||||
if parts.length == 1
|
||||
return proto;
|
||||
end
|
||||
align_pos = proto.index("(") + 1;
|
||||
align = " " * align_pos
|
||||
result = parts.shift;
|
||||
bracket_balance = 0;
|
||||
parts.each{|part|
|
||||
if bracket_balance == 0
|
||||
result += ",\n" + indent + align
|
||||
else
|
||||
result += ", "
|
||||
end
|
||||
result += part
|
||||
bracket_balance += part.count("(") - part.count(")")
|
||||
}
|
||||
return result;
|
||||
end
|
||||
|
||||
# Given the source code array and the index at which an exported symbol was
|
||||
# detected, extracts and outputs the documentation.
|
||||
def docufy(src,i)
|
||||
@@ -38,7 +72,11 @@ def docufy(src,i)
|
||||
name = name.sub("RM_","RedisModule_")
|
||||
proto = src[i].sub("{","").strip+";\n"
|
||||
proto = proto.sub("RM_","RedisModule_")
|
||||
puts "## `#{name}`\n\n"
|
||||
proto = linebreak_proto(proto, " ");
|
||||
# Add a link target with the function name. (We don't trust the exact id of
|
||||
# the generated one, which depends on the Markdown implementation.)
|
||||
puts "<span id=\"#{name}\"></span>\n\n"
|
||||
puts "### `#{name}`\n\n"
|
||||
puts " #{proto}\n"
|
||||
comment = ""
|
||||
while true
|
||||
@@ -50,13 +88,87 @@ def docufy(src,i)
|
||||
puts comment+"\n\n"
|
||||
end
|
||||
|
||||
# Print a comment from line until */ is found, as markdown.
|
||||
def section_doc(src, i)
|
||||
name = get_section_heading(src, i)
|
||||
comment = "<span id=\"#{section_name_to_id(name)}\"></span>\n\n"
|
||||
while true
|
||||
# append line, except if it's a horizontal divider
|
||||
comment = comment + src[i] if src[i] !~ /^[\/ ]?\*{1,2} ?-{50,}/
|
||||
break if src[i] =~ /\*\//
|
||||
i = i+1
|
||||
end
|
||||
comment = markdown(comment)
|
||||
puts comment+"\n\n"
|
||||
end
|
||||
|
||||
# generates an id suitable for links within the page
|
||||
def section_name_to_id(name)
|
||||
return "section-" +
|
||||
name.strip.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-+|-+$/, '')
|
||||
end
|
||||
|
||||
# Returns the name of the first section heading in the comment block for which
|
||||
# is_section_doc(src, i) is true
|
||||
def get_section_heading(src, i)
|
||||
if src[i] =~ /^\/\*\*? \#+ *(.*)/
|
||||
heading = $1
|
||||
elsif src[i+1] =~ /^ ?\* \#+ *(.*)/
|
||||
heading = $1
|
||||
end
|
||||
return heading.gsub(' -- ', ' – ')
|
||||
end
|
||||
|
||||
# Returns true if the line is the start of a generic documentation section. Such
|
||||
# section must start with the # symbol, i.e. a markdown heading, on the first or
|
||||
# the second line.
|
||||
def is_section_doc(src, i)
|
||||
return src[i] =~ /^\/\*\*? \#/ ||
|
||||
(src[i] =~ /^\/\*/ && src[i+1] =~ /^ ?\* \#/)
|
||||
end
|
||||
|
||||
def is_func_line(src, i)
|
||||
line = src[i]
|
||||
return line =~ /RM_/ &&
|
||||
line[0] != ' ' && line[0] != '#' && line[0] != '/' &&
|
||||
src[i-1] =~ /\*\//
|
||||
end
|
||||
|
||||
puts "# Modules API reference\n\n"
|
||||
puts "<!-- This file is generated from module.c using gendoc.rb -->\n\n"
|
||||
src = File.open("../module.c").to_a
|
||||
src.each_with_index{|line,i|
|
||||
if line =~ /RM_/ && line[0] != ' ' && line[0] != '#' && line[0] != '/'
|
||||
if src[i-1] =~ /\*\//
|
||||
docufy(src,i)
|
||||
end
|
||||
src = File.open(File.dirname(__FILE__) ++ "/../module.c").to_a
|
||||
|
||||
# Build function index
|
||||
$index = {}
|
||||
src.each_with_index do |line,i|
|
||||
if is_func_line(src, i)
|
||||
line =~ /RM_([A-z0-9]+)/
|
||||
name = "RedisModule_#{$1}"
|
||||
$index[name] = true
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Print TOC
|
||||
puts "## Sections\n\n"
|
||||
src.each_with_index do |_line,i|
|
||||
if is_section_doc(src, i)
|
||||
name = get_section_heading(src, i)
|
||||
puts "* [#{name}](\##{section_name_to_id(name)})\n"
|
||||
end
|
||||
end
|
||||
puts "* [Function index](#section-function-index)\n\n"
|
||||
|
||||
# Docufy: Print function prototype and markdown docs
|
||||
src.each_with_index do |_line,i|
|
||||
if is_func_line(src, i)
|
||||
docufy(src, i)
|
||||
elsif is_section_doc(src, i)
|
||||
section_doc(src, i)
|
||||
end
|
||||
end
|
||||
|
||||
# Print function index
|
||||
puts "<span id=\"section-function-index\"></span>\n\n"
|
||||
puts "## Function index\n\n"
|
||||
$index.keys.sort.each{|x| puts "* [`#{x}`](\##{x})\n"}
|
||||
puts "\n"
|
||||
|
||||
@@ -168,3 +168,13 @@ const char * monotonicInit() {
|
||||
|
||||
return monotonic_info_string;
|
||||
}
|
||||
|
||||
const char *monotonicInfoString() {
|
||||
return monotonic_info_string;
|
||||
}
|
||||
|
||||
monotonic_clock_type monotonicGetType() {
|
||||
if (getMonotonicUs == getMonotonicUs_posix)
|
||||
return MONOTONIC_CLOCK_POSIX;
|
||||
return MONOTONIC_CLOCK_HW;
|
||||
}
|
||||
|
||||
+10
-1
@@ -24,13 +24,22 @@ typedef uint64_t monotime;
|
||||
/* Retrieve counter of micro-seconds relative to an arbitrary point in time. */
|
||||
extern monotime (*getMonotonicUs)(void);
|
||||
|
||||
typedef enum monotonic_clock_type {
|
||||
MONOTONIC_CLOCK_POSIX,
|
||||
MONOTONIC_CLOCK_HW,
|
||||
} monotonic_clock_type;
|
||||
|
||||
/* Call once at startup to initialize the monotonic clock. Though this only
|
||||
* needs to be called once, it may be called additional times without impact.
|
||||
* Returns a printable string indicating the type of clock initialized.
|
||||
* (The returned string is static and doesn't need to be freed.) */
|
||||
const char * monotonicInit();
|
||||
const char *monotonicInit();
|
||||
|
||||
/* Return a string indicating the type of monotonic clock being used. */
|
||||
const char *monotonicInfoString();
|
||||
|
||||
/* Return the type of monotonic clock being used. */
|
||||
monotonic_clock_type monotonicGetType();
|
||||
|
||||
/* Functions to measure elapsed time. Example:
|
||||
* monotime myTimer;
|
||||
|
||||
+48
-34
@@ -113,34 +113,34 @@ void discardCommand(client *c) {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void beforePropagateMultiOrExec(int multi) {
|
||||
if (multi) {
|
||||
/* Propagating MULTI */
|
||||
serverAssert(!server.propagate_in_transaction);
|
||||
server.propagate_in_transaction = 1;
|
||||
} else {
|
||||
/* Propagating EXEC */
|
||||
serverAssert(server.propagate_in_transaction == 1);
|
||||
server.propagate_in_transaction = 0;
|
||||
}
|
||||
void beforePropagateMulti() {
|
||||
/* Propagating MULTI */
|
||||
serverAssert(!server.propagate_in_transaction);
|
||||
server.propagate_in_transaction = 1;
|
||||
}
|
||||
|
||||
void afterPropagateExec() {
|
||||
/* Propagating EXEC */
|
||||
serverAssert(server.propagate_in_transaction == 1);
|
||||
server.propagate_in_transaction = 0;
|
||||
}
|
||||
|
||||
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
|
||||
* implementation for more information. */
|
||||
void execCommandPropagateMulti(int dbid) {
|
||||
beforePropagateMultiOrExec(1);
|
||||
beforePropagateMulti();
|
||||
propagate(server.multiCommand,dbid,&shared.multi,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
}
|
||||
|
||||
void execCommandPropagateExec(int dbid) {
|
||||
beforePropagateMultiOrExec(0);
|
||||
propagate(server.execCommand,dbid,&shared.exec,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
afterPropagateExec();
|
||||
}
|
||||
|
||||
/* Aborts a transaction, with a specific error message.
|
||||
* The transaction is always aboarted with -EXECABORT so that the client knows
|
||||
* The transaction is always aborted with -EXECABORT so that the client knows
|
||||
* the server exited the multi state, but the actual reason for the abort is
|
||||
* included too.
|
||||
* Note: 'error' may or may not end with \r\n. see addReplyErrorFormat. */
|
||||
@@ -153,8 +153,7 @@ void execCommandAbort(client *c, sds error) {
|
||||
/* Send EXEC to clients waiting data from MONITOR. We did send a MULTI
|
||||
* already, and didn't send any of the queued commands, now we'll just send
|
||||
* EXEC so it is clear that the transaction is over. */
|
||||
if (listLength(server.monitors) && !server.loading)
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
void execCommand(client *c) {
|
||||
@@ -169,17 +168,26 @@ void execCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* EXEC with expired watched key is disallowed*/
|
||||
if (isWatchedKeyExpired(c)) {
|
||||
c->flags |= (CLIENT_DIRTY_CAS);
|
||||
}
|
||||
|
||||
/* Check if we need to abort the EXEC because:
|
||||
* 1) Some WATCHed key was touched.
|
||||
* 2) There was a previous error while queueing commands.
|
||||
* A failed EXEC in the first case returns a multi bulk nil object
|
||||
* (technically it is not an error but a special behavior), while
|
||||
* in the second an EXECABORT error is returned. */
|
||||
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
|
||||
addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
|
||||
shared.nullarray[c->resp]);
|
||||
if (c->flags & (CLIENT_DIRTY_CAS | CLIENT_DIRTY_EXEC)) {
|
||||
if (c->flags & CLIENT_DIRTY_EXEC) {
|
||||
addReplyErrorObject(c, shared.execaborterr);
|
||||
} else {
|
||||
addReply(c, shared.nullarray[c->resp]);
|
||||
}
|
||||
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t old_flags = c->flags;
|
||||
@@ -202,11 +210,9 @@ void execCommand(client *c) {
|
||||
c->cmd = c->mstate.commands[j].cmd;
|
||||
|
||||
/* ACL permissions are also checked at the time of execution in case
|
||||
* they were changed after the commands were ququed. */
|
||||
* they were changed after the commands were queued. */
|
||||
int acl_errpos;
|
||||
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos);
|
||||
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
|
||||
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
|
||||
int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
|
||||
if (acl_retval != ACL_OK) {
|
||||
char *reason;
|
||||
switch (acl_retval) {
|
||||
@@ -217,7 +223,8 @@ void execCommand(client *c) {
|
||||
reason = "no permission to touch the specified keys";
|
||||
break;
|
||||
case ACL_DENIED_CHANNEL:
|
||||
reason = "no permission to publish to the specified channel";
|
||||
reason = "no permission to access one of the channels used "
|
||||
"as arguments";
|
||||
break;
|
||||
default:
|
||||
reason = "no permission";
|
||||
@@ -254,7 +261,6 @@ void execCommand(client *c) {
|
||||
if (server.propagate_in_transaction) {
|
||||
int is_master = server.masterhost == NULL;
|
||||
server.dirty++;
|
||||
beforePropagateMultiOrExec(0);
|
||||
/* If inside the MULTI/EXEC block this instance was suddenly
|
||||
* switched from master to slave (using the SLAVEOF command), the
|
||||
* initial MULTI was propagated into the replication backlog, but the
|
||||
@@ -264,18 +270,10 @@ void execCommand(client *c) {
|
||||
char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
|
||||
feedReplicationBacklog(execcmd,strlen(execcmd));
|
||||
}
|
||||
afterPropagateExec();
|
||||
}
|
||||
|
||||
server.in_exec = 0;
|
||||
|
||||
handle_monitor:
|
||||
/* Send EXEC to clients waiting data from MONITOR. We do it here
|
||||
* since the natural order of commands execution is actually:
|
||||
* MUTLI, EXEC, ... commands inside transaction ...
|
||||
* Instead EXEC is flagged as CMD_SKIP_MONITOR in the command
|
||||
* table, and we do it here with correct ordering. */
|
||||
if (listLength(server.monitors) && !server.loading)
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
/* ===================== WATCH (CAS alike for MULTI/EXEC) ===================
|
||||
@@ -353,6 +351,22 @@ void unwatchAllKeys(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* iterates over the watched_keys list and
|
||||
* look for an expired key . */
|
||||
int isWatchedKeyExpired(client *c) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
watchedKey *wk;
|
||||
if (listLength(c->watched_keys) == 0) return 0;
|
||||
listRewind(c->watched_keys,&li);
|
||||
while ((ln = listNext(&li))) {
|
||||
wk = listNodeValue(ln);
|
||||
if (keyIsExpired(wk->db, wk->key)) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* "Touch" a key, so that if this key is being WATCHed by some client the
|
||||
* next EXEC will fail. */
|
||||
void touchWatchedKey(redisDb *db, robj *key) {
|
||||
|
||||
+196
-48
@@ -107,6 +107,15 @@ static void clientSetDefaultAuth(client *c) {
|
||||
!(c->user->flags & USER_FLAG_DISABLED);
|
||||
}
|
||||
|
||||
int authRequired(client *c) {
|
||||
/* Check if the user is authenticated. This check is skipped in case
|
||||
* the default user is flagged as "nopass" and is active. */
|
||||
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||
(DefaultUser->flags & USER_FLAG_DISABLED)) &&
|
||||
!c->authenticated;
|
||||
return auth_required;
|
||||
}
|
||||
|
||||
client *createClient(connection *conn) {
|
||||
client *c = zmalloc(sizeof(client));
|
||||
|
||||
@@ -154,10 +163,12 @@ client *createClient(connection *conn) {
|
||||
c->read_reploff = 0;
|
||||
c->repl_ack_off = 0;
|
||||
c->repl_ack_time = 0;
|
||||
c->repl_last_partial_write = 0;
|
||||
c->slave_listening_port = 0;
|
||||
c->slave_addr = NULL;
|
||||
c->slave_capa = SLAVE_CAPA_NONE;
|
||||
c->reply = listCreate();
|
||||
c->deferred_reply_errors = NULL;
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
@@ -249,8 +260,10 @@ int prepareClientToWrite(client *c) {
|
||||
/* If CLIENT_CLOSE_ASAP flag is set, we need not write anything. */
|
||||
if (c->flags & CLIENT_CLOSE_ASAP) return C_ERR;
|
||||
|
||||
/* CLIENT REPLY OFF / SKIP handling: don't send replies. */
|
||||
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
|
||||
/* CLIENT REPLY OFF / SKIP handling: don't send replies.
|
||||
* CLIENT_PUSHING handling: disables the reply silencing flags. */
|
||||
if ((c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) &&
|
||||
!(c->flags & CLIENT_PUSHING)) return C_ERR;
|
||||
|
||||
/* Masters don't receive replies, unless CLIENT_MASTER_FORCE_REPLY flag
|
||||
* is set. */
|
||||
@@ -331,8 +344,9 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
|
||||
memcpy(tail->buf, s, len);
|
||||
listAddNodeTail(c->reply, tail);
|
||||
c->reply_bytes += tail->size;
|
||||
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@@ -406,6 +420,18 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
|
||||
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.) */
|
||||
void afterErrorReply(client *c, const char *s, size_t len) {
|
||||
/* Module clients fall into two categories:
|
||||
* Calls to RM_Call, in which case the error isn't being returned to a client, so should not be counted.
|
||||
* Module thread safe context calls to RM_ReplyWithError, which will be added to a real client by the main thread later. */
|
||||
if (c->flags & CLIENT_MODULE) {
|
||||
if (!c->deferred_reply_errors) {
|
||||
c->deferred_reply_errors = listCreate();
|
||||
listSetFreeMethod(c->deferred_reply_errors, (void (*)(void*))sdsfree);
|
||||
}
|
||||
listAddNodeTail(c->deferred_reply_errors, sdsnewlen(s, len));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Increment the global error counter */
|
||||
server.stat_total_error_replies++;
|
||||
/* Increment the error stats
|
||||
@@ -562,7 +588,7 @@ void *addReplyDeferredLen(client *c) {
|
||||
|
||||
void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
listNode *ln = (listNode*)node;
|
||||
clientReplyBlock *next;
|
||||
clientReplyBlock *next, *prev;
|
||||
|
||||
/* Abort when *node is NULL: when the client should not accept writes
|
||||
* we return NULL in addReplyDeferredLen() */
|
||||
@@ -571,14 +597,31 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
|
||||
/* Normally we fill this dummy NULL node, added by addReplyDeferredLen(),
|
||||
* with a new buffer structure containing the protocol needed to specify
|
||||
* the length of the array following. However sometimes when there is
|
||||
* little memory to move, we may instead remove this NULL node, and prefix
|
||||
* our protocol in the node immediately after to it, in order to save a
|
||||
* write(2) syscall later. Conditions needed to do it:
|
||||
* the length of the array following. However sometimes there might be room
|
||||
* in the previous/next node so we can instead remove this NULL node, and
|
||||
* suffix/prefix our data in the node immediately before/after it, in order
|
||||
* to save a write(2) syscall later. Conditions needed to do it:
|
||||
*
|
||||
* - The prev node is non-NULL and has space in it or
|
||||
* - The next node is non-NULL,
|
||||
* - It has enough room already allocated
|
||||
* - And not too large (avoid large memmove) */
|
||||
if (ln->prev != NULL && (prev = listNodeValue(ln->prev)) &&
|
||||
prev->size - prev->used > 0)
|
||||
{
|
||||
size_t len_to_copy = prev->size - prev->used;
|
||||
if (len_to_copy > length)
|
||||
len_to_copy = length;
|
||||
memcpy(prev->buf + prev->used, s, len_to_copy);
|
||||
prev->used += len_to_copy;
|
||||
length -= len_to_copy;
|
||||
if (length == 0) {
|
||||
listDelNode(c->reply, ln);
|
||||
return;
|
||||
}
|
||||
s += len_to_copy;
|
||||
}
|
||||
|
||||
if (ln->next != NULL && (next = listNodeValue(ln->next)) &&
|
||||
next->size - next->used >= length &&
|
||||
next->used < PROTO_REPLY_CHUNK_BYTES * 4)
|
||||
@@ -596,8 +639,9 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
memcpy(buf->buf, s, length);
|
||||
listNodeValue(ln) = buf;
|
||||
c->reply_bytes += buf->size;
|
||||
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
|
||||
/* Populate the length object and try gluing it to the next chunk. */
|
||||
@@ -608,6 +652,24 @@ void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
|
||||
* we return NULL in addReplyDeferredLen() */
|
||||
if (node == NULL) return;
|
||||
|
||||
/* Things like *2\r\n, %3\r\n or ~4\r\n are emitted very often by the protocol
|
||||
* so we have a few shared objects to use if the integer is small
|
||||
* like it is most of the times. */
|
||||
const size_t hdr_len = OBJ_SHARED_HDR_STRLEN(length);
|
||||
const int opt_hdr = length < OBJ_SHARED_BULKHDR_LEN;
|
||||
if (prefix == '*' && opt_hdr) {
|
||||
setDeferredReply(c, node, shared.mbulkhdr[length]->ptr, hdr_len);
|
||||
return;
|
||||
}
|
||||
if (prefix == '%' && opt_hdr) {
|
||||
setDeferredReply(c, node, shared.maphdr[length]->ptr, hdr_len);
|
||||
return;
|
||||
}
|
||||
if (prefix == '~' && opt_hdr) {
|
||||
setDeferredReply(c, node, shared.sethdr[length]->ptr, hdr_len);
|
||||
return;
|
||||
}
|
||||
|
||||
char lenstr[128];
|
||||
size_t lenstr_len = sprintf(lenstr, "%c%ld\r\n", prefix, length);
|
||||
setDeferredReply(c, node, lenstr, lenstr_len);
|
||||
@@ -629,14 +691,13 @@ void setDeferredSetLen(client *c, void *node, long length) {
|
||||
}
|
||||
|
||||
void setDeferredAttributeLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '|';
|
||||
if (c->resp == 2) length *= 2;
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
setDeferredAggregateLen(c,node,length,'|');
|
||||
}
|
||||
|
||||
void setDeferredPushLen(client *c, void *node, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '>';
|
||||
setDeferredAggregateLen(c,node,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
setDeferredAggregateLen(c,node,length,'>');
|
||||
}
|
||||
|
||||
/* Add a double as a bulk reply */
|
||||
@@ -665,6 +726,16 @@ void addReplyDouble(client *c, double d) {
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyBigNum(client *c, const char* num, size_t len) {
|
||||
if (c->resp == 2) {
|
||||
addReplyBulkCBuffer(c, num, len);
|
||||
} else {
|
||||
addReplyProto(c,"(",1);
|
||||
addReplyProto(c,num,len);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a long double as a bulk reply, but uses a human readable formatting
|
||||
* of the double instead of exposing the crude behavior of doubles to the
|
||||
* dear user. */
|
||||
@@ -691,11 +762,19 @@ void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
|
||||
/* Things like $3\r\n or *2\r\n are emitted very often by the protocol
|
||||
* so we have a few shared objects to use if the integer is small
|
||||
* like it is most of the times. */
|
||||
if (prefix == '*' && ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0) {
|
||||
addReply(c,shared.mbulkhdr[ll]);
|
||||
const int opt_hdr = ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0;
|
||||
const size_t hdr_len = OBJ_SHARED_HDR_STRLEN(ll);
|
||||
if (prefix == '*' && opt_hdr) {
|
||||
addReplyProto(c,shared.mbulkhdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
} else if (prefix == '$' && ll < OBJ_SHARED_BULKHDR_LEN && ll >= 0) {
|
||||
addReply(c,shared.bulkhdr[ll]);
|
||||
} else if (prefix == '$' && opt_hdr) {
|
||||
addReplyProto(c,shared.bulkhdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
} else if (prefix == '%' && opt_hdr) {
|
||||
addReplyProto(c,shared.maphdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
} else if (prefix == '~' && opt_hdr) {
|
||||
addReplyProto(c,shared.sethdr[ll]->ptr,hdr_len);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -736,14 +815,14 @@ void addReplySetLen(client *c, long length) {
|
||||
}
|
||||
|
||||
void addReplyAttributeLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '|';
|
||||
if (c->resp == 2) length *= 2;
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
addReplyAggregateLen(c,length,'|');
|
||||
}
|
||||
|
||||
void addReplyPushLen(client *c, long length) {
|
||||
int prefix = c->resp == 2 ? '*' : '>';
|
||||
addReplyAggregateLen(c,length,prefix);
|
||||
serverAssert(c->resp >= 3);
|
||||
serverAssertWithInfo(c, NULL, c->flags & CLIENT_PUSHING);
|
||||
addReplyAggregateLen(c,length,'>');
|
||||
}
|
||||
|
||||
void addReplyNull(client *c) {
|
||||
@@ -928,8 +1007,14 @@ void AddReplyFromClient(client *dst, client *src) {
|
||||
src->reply_bytes = 0;
|
||||
src->bufpos = 0;
|
||||
|
||||
if (src->deferred_reply_errors) {
|
||||
deferredAfterErrorReply(dst, src->deferred_reply_errors);
|
||||
listRelease(src->deferred_reply_errors);
|
||||
src->deferred_reply_errors = NULL;
|
||||
}
|
||||
|
||||
/* Check output buffer limits */
|
||||
asyncCloseClientOnOutputBufferLimitReached(dst);
|
||||
closeClientOnOutputBufferLimitReached(dst, 1);
|
||||
}
|
||||
|
||||
/* Copy 'src' client output buffers into 'dst' client output buffers.
|
||||
@@ -944,6 +1029,18 @@ void copyClientOutputBuffer(client *dst, client *src) {
|
||||
dst->reply_bytes = src->reply_bytes;
|
||||
}
|
||||
|
||||
/* Append the listed errors to the server error statistics. the input
|
||||
* list is not modified and remains the responsibility of the caller. */
|
||||
void deferredAfterErrorReply(client *c, list *errors) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(errors,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds err = ln->value;
|
||||
afterErrorReply(c, err, sdslen(err));
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if the specified client has pending reply buffers to write to
|
||||
* the socket. */
|
||||
int clientHasPendingReplies(client *c) {
|
||||
@@ -1338,6 +1435,8 @@ void freeClient(client *c) {
|
||||
listRelease(c->reply);
|
||||
freeClientArgv(c);
|
||||
freeClientOriginalArgv(c);
|
||||
if (c->deferred_reply_errors)
|
||||
listRelease(c->deferred_reply_errors);
|
||||
|
||||
/* Unlink the client: this will close the socket, remove the I/O
|
||||
* handlers, and remove references of the client from different
|
||||
@@ -1531,9 +1630,7 @@ int writeToClient(client *c, int handler_installed) {
|
||||
}
|
||||
atomicIncr(server.stat_net_output_bytes, totwritten);
|
||||
if (nwritten == -1) {
|
||||
if (connGetState(c->conn) == CONN_STATE_CONNECTED) {
|
||||
nwritten = 0;
|
||||
} else {
|
||||
if (connGetState(c->conn) != CONN_STATE_CONNECTED) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error writing to client: %s", connGetLastError(c->conn));
|
||||
freeClientAsync(c);
|
||||
@@ -1626,6 +1723,10 @@ void resetClient(client *c) {
|
||||
c->multibulklen = 0;
|
||||
c->bulklen = -1;
|
||||
|
||||
if (c->deferred_reply_errors)
|
||||
listRelease(c->deferred_reply_errors);
|
||||
c->deferred_reply_errors = NULL;
|
||||
|
||||
/* We clear the ASKING flag as well if we are not inside a MULTI, and
|
||||
* if what we just executed is not the ASKING command itself. */
|
||||
if (!(c->flags & CLIENT_MULTI) && prevcmd != askingCommand)
|
||||
@@ -1834,6 +1935,10 @@ int processMultibulkBuffer(client *c) {
|
||||
addReplyError(c,"Protocol error: invalid multibulk length");
|
||||
setProtocolError("invalid mbulk count",c);
|
||||
return C_ERR;
|
||||
} else if (ll > 10 && authRequired(c)) {
|
||||
addReplyError(c, "Protocol error: unauthenticated multibulk length");
|
||||
setProtocolError("unauth mbulk count", c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = (newline-c->querybuf)+2;
|
||||
@@ -1881,6 +1986,10 @@ int processMultibulkBuffer(client *c) {
|
||||
addReplyError(c,"Protocol error: invalid bulk length");
|
||||
setProtocolError("invalid bulk length",c);
|
||||
return C_ERR;
|
||||
} else if (ll > 16384 && authRequired(c)) {
|
||||
addReplyError(c, "Protocol error: unauthenticated bulk length");
|
||||
setProtocolError("unauth bulk length", c);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
c->qb_pos = newline-c->querybuf+2;
|
||||
@@ -1948,22 +2057,23 @@ int processMultibulkBuffer(client *c) {
|
||||
* 2. In the case of master clients, the replication offset is updated.
|
||||
* 3. Propagate commands we got from our master to replicas down the line. */
|
||||
void commandProcessed(client *c) {
|
||||
/* If client is blocked(including paused), just return avoid reset and replicate.
|
||||
*
|
||||
* 1. Don't reset the client structure for blocked clients, so that the reply
|
||||
* callback will still be able to access the client argv and argc fields.
|
||||
* The client will be reset in unblockClient().
|
||||
* 2. Don't update replication offset or propagate commands to replicas,
|
||||
* since we have not applied the command. */
|
||||
if (c->flags & CLIENT_BLOCKED) return;
|
||||
|
||||
resetClient(c);
|
||||
|
||||
long long prev_offset = c->reploff;
|
||||
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
|
||||
/* Update the applied replication offset of our master. */
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
|
||||
}
|
||||
|
||||
/* Don't reset the client structure for clients blocked in a
|
||||
* module blocking command, so that the reply callback will
|
||||
* still be able to access the client argv and argc field.
|
||||
* The client will be reset in unblockClientFromModule(). */
|
||||
if (!(c->flags & CLIENT_BLOCKED) ||
|
||||
(c->btype != BLOCKED_MODULE && c->btype != BLOCKED_PAUSE))
|
||||
{
|
||||
resetClient(c);
|
||||
}
|
||||
|
||||
/* If the client is a master we need to compute the difference
|
||||
* between the applied offset before and after processing the buffer,
|
||||
* to understand how much of the replication stream was actually
|
||||
@@ -1990,12 +2100,20 @@ void commandProcessed(client *c) {
|
||||
* of processing the command, otherwise C_OK is returned. */
|
||||
int processCommandAndResetClient(client *c) {
|
||||
int deadclient = 0;
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = c;
|
||||
if (processCommand(c) == C_OK) {
|
||||
commandProcessed(c);
|
||||
}
|
||||
if (server.current_client == NULL) deadclient = 1;
|
||||
server.current_client = NULL;
|
||||
/*
|
||||
* Restore the old client, this is needed because when a script
|
||||
* times out, we will get into this code from processEventsWhileBlocked.
|
||||
* Which will cause to set the server.current_client. If not restored
|
||||
* we will return 1 to our caller which will falsely indicate the client
|
||||
* is dead and will stop reading from its buffer.
|
||||
*/
|
||||
server.current_client = old_client;
|
||||
/* performEvictions may flush slave output buffers. This may
|
||||
* result in a slave, that may be the active client, to be
|
||||
* freed. */
|
||||
@@ -2646,7 +2764,7 @@ NULL
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&id,NULL)
|
||||
!= C_OK) return;
|
||||
struct client *target = lookupClientByID(id);
|
||||
if (target && target->flags & CLIENT_BLOCKED) {
|
||||
if (target && target->flags & CLIENT_BLOCKED && moduleBlockedClientMayTimeout(target)) {
|
||||
if (unblock_error)
|
||||
addReplyError(target,
|
||||
"-UNBLOCKED client unblocked via CLIENT UNBLOCK");
|
||||
@@ -2941,6 +3059,8 @@ void helloCommand(client *c) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
const char *opt = c->argv[j]->ptr;
|
||||
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
|
||||
redactClientCommandArgument(c, j+1);
|
||||
redactClientCommandArgument(c, j+2);
|
||||
if (ACLAuthenticateUser(c, c->argv[j+1], c->argv[j+2]) == C_ERR) {
|
||||
addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
|
||||
return;
|
||||
@@ -3007,7 +3127,7 @@ void securityWarningCommand(client *c) {
|
||||
static time_t logged_time;
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (labs(now-logged_time) > 60) {
|
||||
if (llabs(now-logged_time) > 60) {
|
||||
serverLog(LL_WARNING,"Possible SECURITY ATTACK detected. It looks like somebody is sending POST or Host: commands to Redis. This is likely due to an attacker attempting to use Cross Protocol Scripting to compromise your Redis instance. Connection aborted.");
|
||||
logged_time = now;
|
||||
}
|
||||
@@ -3027,6 +3147,15 @@ static void retainOriginalCommandVector(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Redact a given argument to prevent it from being shown
|
||||
* in the slowlog. This information is stored in the
|
||||
* original_argv array. */
|
||||
void redactClientCommandArgument(client *c, int argc) {
|
||||
retainOriginalCommandVector(c);
|
||||
decrRefCount(c->argv[argc]);
|
||||
c->original_argv[argc] = shared.redacted;
|
||||
}
|
||||
|
||||
/* Rewrite the command vector of the client. All the new objects ref count
|
||||
* is incremented. The old command vector is freed, and the old objects
|
||||
* ref count is decremented. */
|
||||
@@ -3196,18 +3325,33 @@ int checkClientOutputBufferLimits(client *c) {
|
||||
*
|
||||
* Note: we need to close the client asynchronously because this function is
|
||||
* called from contexts where the client can't be freed safely, i.e. from the
|
||||
* lower level functions pushing data inside the client output buffers. */
|
||||
void asyncCloseClientOnOutputBufferLimitReached(client *c) {
|
||||
if (!c->conn) return; /* It is unsafe to free fake clients. */
|
||||
* lower level functions pushing data inside the client output buffers.
|
||||
* When `async` is set to 0, we close the client immediately, this is
|
||||
* useful when called from cron.
|
||||
*
|
||||
* Returns 1 if client was (flagged) closed. */
|
||||
int closeClientOnOutputBufferLimitReached(client *c, int async) {
|
||||
if (!c->conn) return 0; /* It is unsafe to free fake clients. */
|
||||
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
|
||||
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
|
||||
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return 0;
|
||||
if (checkClientOutputBufferLimits(c)) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
|
||||
freeClientAsync(c);
|
||||
serverLog(LL_WARNING,"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.", client);
|
||||
if (async) {
|
||||
freeClientAsync(c);
|
||||
serverLog(LL_WARNING,
|
||||
"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.",
|
||||
client);
|
||||
} else {
|
||||
freeClient(c);
|
||||
serverLog(LL_WARNING,
|
||||
"Client %s closed for overcoming of output buffer limits.",
|
||||
client);
|
||||
}
|
||||
sdsfree(client);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper function used by performEvictions() in order to flush slaves
|
||||
@@ -3286,6 +3430,7 @@ void unpauseClients(void) {
|
||||
client *c;
|
||||
|
||||
server.client_pause_type = CLIENT_PAUSE_OFF;
|
||||
server.client_pause_end_time = 0;
|
||||
|
||||
/* Unblock all of the clients so they are reprocessed. */
|
||||
listRewind(server.paused_clients,&li);
|
||||
@@ -3304,6 +3449,8 @@ int areClientsPaused(void) {
|
||||
* if it has. Also returns true if clients are now paused and false
|
||||
* otherwise. */
|
||||
int checkClientPauseTimeoutAndReturnIfPaused(void) {
|
||||
if (!areClientsPaused())
|
||||
return 0;
|
||||
if (server.client_pause_end_time < server.mstime) {
|
||||
unpauseClients();
|
||||
}
|
||||
@@ -3332,7 +3479,7 @@ void processEventsWhileBlocked(void) {
|
||||
/* Note: when we are processing events while blocked (for instance during
|
||||
* busy Lua scripts), we set a global flag. When such flag is set, we
|
||||
* avoid handling the read part of clients using threaded I/O.
|
||||
* See https://github.com/antirez/redis/issues/6988 for more info. */
|
||||
* See https://github.com/redis/redis/issues/6988 for more info. */
|
||||
ProcessingEventsWhileBlocked = 1;
|
||||
while (iterations--) {
|
||||
long long startval = server.events_processed_while_blocked;
|
||||
@@ -3603,7 +3750,7 @@ int postponeClientRead(client *c) {
|
||||
if (server.io_threads_active &&
|
||||
server.io_threads_do_reads &&
|
||||
!ProcessingEventsWhileBlocked &&
|
||||
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
|
||||
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ|CLIENT_BLOCKED)))
|
||||
{
|
||||
c->flags |= CLIENT_PENDING_READ;
|
||||
listAddNodeHead(server.clients_pending_read,c);
|
||||
@@ -3667,6 +3814,7 @@ int handleClientsWithPendingReadsUsingThreads(void) {
|
||||
c->flags &= ~CLIENT_PENDING_READ;
|
||||
listDelNode(server.clients_pending_read,ln);
|
||||
|
||||
serverAssert(!(c->flags & CLIENT_BLOCKED));
|
||||
if (processPendingCommandsAndResetClient(c) == C_ERR) {
|
||||
/* If the client is no longer valid, we avoid
|
||||
* processing the client later. So we just go
|
||||
|
||||
@@ -56,6 +56,7 @@ int keyspaceEventsStringToFlags(char *classes) {
|
||||
case 'E': flags |= NOTIFY_KEYEVENT; break;
|
||||
case 't': flags |= NOTIFY_STREAM; break;
|
||||
case 'm': flags |= NOTIFY_KEY_MISS; break;
|
||||
case 'd': flags |= NOTIFY_MODULE; break;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@@ -82,6 +83,7 @@ sds keyspaceEventsFlagsToString(int flags) {
|
||||
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
|
||||
if (flags & NOTIFY_MODULE) res = sdscatlen(res,"d",1);
|
||||
}
|
||||
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
|
||||
+28
-8
@@ -123,6 +123,21 @@ robj *createStringObject(const char *ptr, size_t len) {
|
||||
return createRawStringObject(ptr,len);
|
||||
}
|
||||
|
||||
/* Same as CreateRawStringObject, can return NULL if allocation fails */
|
||||
robj *tryCreateRawStringObject(const char *ptr, size_t len) {
|
||||
sds str = sdstrynewlen(ptr,len);
|
||||
if (!str) return NULL;
|
||||
return createObject(OBJ_STRING, str);
|
||||
}
|
||||
|
||||
/* Same as createStringObject, can return NULL if allocation fails */
|
||||
robj *tryCreateStringObject(const char *ptr, size_t len) {
|
||||
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
|
||||
return createEmbeddedStringObject(ptr,len);
|
||||
else
|
||||
return tryCreateRawStringObject(ptr,len);
|
||||
}
|
||||
|
||||
/* Create a string object from a long long value. When possible returns a
|
||||
* shared integer object, or at least an integer encoded one.
|
||||
*
|
||||
@@ -415,7 +430,7 @@ void trimStringObjectIfNeeded(robj *o) {
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(o->ptr) > sdslen(o->ptr)/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,7 +742,11 @@ int getRangeLongFromObjectOrReply(client *c, robj *o, long min, long max, long *
|
||||
}
|
||||
|
||||
int getPositiveLongFromObjectOrReply(client *c, robj *o, long *target, const char *msg) {
|
||||
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, msg);
|
||||
if (msg) {
|
||||
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, msg);
|
||||
} else {
|
||||
return getRangeLongFromObjectOrReply(c, o, 0, LONG_MAX, target, "value is out of range, must be positive");
|
||||
}
|
||||
}
|
||||
|
||||
int getIntFromObjectOrReply(client *c, robj *o, int *target, const char *msg) {
|
||||
@@ -832,7 +851,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
if (samples) asize += (double)elesize/samples*dictSize(d);
|
||||
} else if (o->encoding == OBJ_ENCODING_INTSET) {
|
||||
intset *is = o->ptr;
|
||||
asize = sizeof(*o)+sizeof(*is)+is->encoding*is->length;
|
||||
asize = sizeof(*o)+sizeof(*is)+(size_t)is->encoding*is->length;
|
||||
} else {
|
||||
serverPanic("Unknown set encoding");
|
||||
}
|
||||
@@ -877,7 +896,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
}
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
stream *s = o->ptr;
|
||||
asize = sizeof(*o);
|
||||
asize = sizeof(*o)+sizeof(*s);
|
||||
asize += streamRadixTreeMemoryUsage(s->rax);
|
||||
|
||||
/* Now we have to add the listpacks. The last listpack is often non
|
||||
@@ -890,7 +909,8 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
size_t lpsize = 0, samples = 0;
|
||||
while(samples < sample_size && raxNext(&ri)) {
|
||||
unsigned char *lp = ri.data;
|
||||
lpsize += lpBytes(lp);
|
||||
/* Use the allocated size, since we overprovision the node initially. */
|
||||
lpsize += zmalloc_size(lp);
|
||||
samples++;
|
||||
}
|
||||
if (s->rax->numele <= samples) {
|
||||
@@ -902,7 +922,8 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
* if there are a few elements in the radix tree. */
|
||||
raxSeek(&ri,"$",NULL,0);
|
||||
raxNext(&ri);
|
||||
asize += lpBytes(ri.data);
|
||||
/* Use the allocated size, since we overprovision the node initially. */
|
||||
asize += zmalloc_size(ri.data);
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
@@ -1227,8 +1248,7 @@ robj *objectCommandLookup(client *c, robj *key) {
|
||||
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
robj *o = objectCommandLookup(c,key);
|
||||
|
||||
if (!o) addReply(c, reply);
|
||||
if (!o) SentReplyOnKeyMiss(c, reply);
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -62,7 +62,7 @@ static inline void swapfunc (char *, char *, size_t, int);
|
||||
} while (--i > 0); \
|
||||
}
|
||||
|
||||
#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
|
||||
#define SWAPINIT(a, es) swaptype = (uintptr_t)a % sizeof(long) || \
|
||||
es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
|
||||
|
||||
static inline void
|
||||
|
||||
+26
-24
@@ -41,6 +41,8 @@ int clientSubscriptionsCount(client *c);
|
||||
* to send a special message (for instance an Array type) by using the
|
||||
* addReply*() API family. */
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -48,12 +50,15 @@ void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
if (msg) addReplyBulk(c,msg);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send a pubsub message of type "pmessage" to the client. The difference
|
||||
* with the "message" type delivered by addReplyPubsubMessage() is that
|
||||
* this message format also includes the pattern that matched the message. */
|
||||
void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[4]);
|
||||
else
|
||||
@@ -62,10 +67,13 @@ void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
addReplyBulk(c,pat);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub subscription notification to the client. */
|
||||
void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -73,6 +81,7 @@ void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub unsubscription notification to the client.
|
||||
@@ -80,6 +89,8 @@ void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
* unsubscribe command but there are no channels to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -90,10 +101,13 @@ void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern subscription notification to the client. */
|
||||
void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -101,6 +115,7 @@ void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern unsubscription notification to the client.
|
||||
@@ -108,6 +123,8 @@ void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
* punsubscribe command but there are no pattern to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -118,6 +135,7 @@ void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -250,18 +268,20 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
/* Unsubscribe from all the channels. Return the number of channels the
|
||||
* client was subscribed to. */
|
||||
int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
int count = 0;
|
||||
if (dictSize(c->pubsub_channels) > 0) {
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *channel = dictGetKey(de);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *channel = dictGetKey(de);
|
||||
|
||||
count += pubsubUnsubscribeChannel(c,channel,notify);
|
||||
count += pubsubUnsubscribeChannel(c,channel,notify);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
if (notify && count == 0) addReplyPubsubUnsubscribed(c,NULL);
|
||||
dictReleaseIterator(di);
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -329,21 +349,6 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
return receivers;
|
||||
}
|
||||
|
||||
/* This wraps handling ACL channel permissions for the given client. */
|
||||
int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal) {
|
||||
/* Check if the user can run the command according to the current
|
||||
* ACLs. */
|
||||
int acl_chanpos;
|
||||
int acl_retval = ACLCheckPubsubPerm(c,idx,count,literal,&acl_chanpos);
|
||||
if (acl_retval == ACL_DENIED_CHANNEL) {
|
||||
addACLLogEntry(c,acl_retval,acl_chanpos,NULL);
|
||||
addReplyError(c,
|
||||
"-NOPERM this user has no permissions to access "
|
||||
"one of the channels used as arguments");
|
||||
}
|
||||
return acl_retval;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub commands implementation
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -351,7 +356,6 @@ int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal)
|
||||
/* SUBSCRIBE channel [channel ...] */
|
||||
void subscribeCommand(client *c) {
|
||||
int j;
|
||||
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,0) != ACL_OK) return;
|
||||
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
|
||||
/**
|
||||
* A client that has CLIENT_DENY_BLOCKING flag on
|
||||
@@ -385,7 +389,6 @@ void unsubscribeCommand(client *c) {
|
||||
/* PSUBSCRIBE pattern [pattern ...] */
|
||||
void psubscribeCommand(client *c) {
|
||||
int j;
|
||||
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,1) != ACL_OK) return;
|
||||
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
|
||||
/**
|
||||
* A client that has CLIENT_DENY_BLOCKING flag on
|
||||
@@ -418,7 +421,6 @@ void punsubscribeCommand(client *c) {
|
||||
|
||||
/* PUBLISH <channel> <message> */
|
||||
void publishCommand(client *c) {
|
||||
if (pubsubCheckACLPermissionsOrReply(c,1,1,0) != ACL_OK) return;
|
||||
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
||||
if (server.cluster_enabled)
|
||||
clusterPropagatePublish(c->argv[1],c->argv[2]);
|
||||
|
||||
+190
-185
@@ -45,11 +45,16 @@
|
||||
#define REDIS_STATIC static
|
||||
#endif
|
||||
|
||||
/* Optimization levels for size-based filling */
|
||||
/* Optimization levels for size-based filling.
|
||||
* Note that the largest possible limit is 16k, so even if each record takes
|
||||
* just one byte, it still won't overflow the 16 bit count field. */
|
||||
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
||||
|
||||
/* Maximum size in bytes of any multi-element ziplist.
|
||||
* Larger values will live in their own isolated ziplists. */
|
||||
* Larger values will live in their own isolated ziplists.
|
||||
* This is used only if we're limited by record count. when we're limited by
|
||||
* size, the maximum limit is bigger, but still safe.
|
||||
* 8k is a recommended / default size limit */
|
||||
#define SIZE_SAFETY_LIMIT 8192
|
||||
|
||||
/* Minimum ziplist size in bytes for attempting compression. */
|
||||
@@ -315,7 +320,9 @@ REDIS_STATIC void __quicklistCompress(const quicklist *quicklist,
|
||||
if (forward == node || reverse == node)
|
||||
in_depth = 1;
|
||||
|
||||
if (forward == reverse)
|
||||
/* We passed into compress depth of opposite side of the quicklist
|
||||
* so there's no need to compress anything and we can exit. */
|
||||
if (forward == reverse || forward->next == reverse)
|
||||
return;
|
||||
|
||||
forward = forward->next;
|
||||
@@ -325,11 +332,9 @@ REDIS_STATIC void __quicklistCompress(const quicklist *quicklist,
|
||||
if (!in_depth)
|
||||
quicklistCompressNode(node);
|
||||
|
||||
if (depth > 2) {
|
||||
/* At this point, forward and reverse are one node beyond depth */
|
||||
quicklistCompressNode(forward);
|
||||
quicklistCompressNode(reverse);
|
||||
}
|
||||
/* At this point, forward and reverse are one node beyond depth */
|
||||
quicklistCompressNode(forward);
|
||||
quicklistCompressNode(reverse);
|
||||
}
|
||||
|
||||
#define quicklistCompress(_ql, _node) \
|
||||
@@ -380,10 +385,11 @@ REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
|
||||
quicklist->head = quicklist->tail = new_node;
|
||||
}
|
||||
|
||||
/* Update len first, so in __quicklistCompress we know exactly len */
|
||||
quicklist->len++;
|
||||
|
||||
if (old_node)
|
||||
quicklistCompress(quicklist, old_node);
|
||||
|
||||
quicklist->len++;
|
||||
}
|
||||
|
||||
/* Wrappers for node inserting around existing node. */
|
||||
@@ -443,6 +449,8 @@ REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
|
||||
unsigned int new_sz = node->sz + sz + ziplist_overhead;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
|
||||
return 1;
|
||||
/* when we return 1 above we know that the limit is a size limit (which is
|
||||
* safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(new_sz))
|
||||
return 0;
|
||||
else if ((int)node->count < fill)
|
||||
@@ -462,6 +470,8 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
unsigned int merge_sz = a->sz + b->sz - 11;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill)))
|
||||
return 1;
|
||||
/* when we return 1 above we know that the limit is a size limit (which is
|
||||
* safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(merge_sz))
|
||||
return 0;
|
||||
else if ((int)(a->count + b->count) <= fill)
|
||||
@@ -481,6 +491,7 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
* Returns 1 if new head created. */
|
||||
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_head = quicklist->head;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
|
||||
quicklist->head->zl =
|
||||
@@ -504,6 +515,7 @@ int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
* Returns 1 if new tail created. */
|
||||
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_tail = quicklist->tail;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
|
||||
quicklist->tail->zl =
|
||||
@@ -602,15 +614,16 @@ REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
|
||||
quicklist->head = node->next;
|
||||
}
|
||||
|
||||
/* Update len first, so in __quicklistCompress we know exactly len */
|
||||
quicklist->len--;
|
||||
quicklist->count -= node->count;
|
||||
|
||||
/* If we deleted a node within our compress depth, we
|
||||
* now have compressed nodes needing to be decompressed. */
|
||||
__quicklistCompress(quicklist, NULL);
|
||||
|
||||
quicklist->count -= node->count;
|
||||
|
||||
zfree(node->zl);
|
||||
zfree(node);
|
||||
quicklist->len--;
|
||||
}
|
||||
|
||||
/* Delete one entry from list given the node for the entry and a pointer
|
||||
@@ -845,6 +858,7 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
|
||||
int fill = quicklist->fill;
|
||||
quicklistNode *node = entry->node;
|
||||
quicklistNode *new_node = NULL;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
|
||||
if (!node) {
|
||||
/* we have no reference node, so let's create only node in the list */
|
||||
@@ -999,7 +1013,7 @@ int quicklistDelRange(quicklist *quicklist, const long start,
|
||||
* can just delete the entire node without ziplist math. */
|
||||
delete_entire_node = 1;
|
||||
del = node->count;
|
||||
} else if (entry.offset >= 0 && extent >= node->count) {
|
||||
} else if (entry.offset >= 0 && extent + entry.offset >= node->count) {
|
||||
/* If deleting more nodes after this one, calculate delete based
|
||||
* on size of current node. */
|
||||
del = node->count - entry.offset;
|
||||
@@ -1296,17 +1310,24 @@ void quicklistRotate(quicklist *quicklist) {
|
||||
|
||||
/* First, get the tail entry */
|
||||
unsigned char *p = ziplistIndex(quicklist->tail->zl, -1);
|
||||
unsigned char *value;
|
||||
unsigned char *value, *tmp;
|
||||
long long longval;
|
||||
unsigned int sz;
|
||||
char longstr[32] = {0};
|
||||
ziplistGet(p, &value, &sz, &longval);
|
||||
ziplistGet(p, &tmp, &sz, &longval);
|
||||
|
||||
/* If value found is NULL, then ziplistGet populated longval instead */
|
||||
if (!value) {
|
||||
if (!tmp) {
|
||||
/* Write the longval as a string so we can re-add it */
|
||||
sz = ll2string(longstr, sizeof(longstr), longval);
|
||||
value = (unsigned char *)longstr;
|
||||
} else if (quicklist->len == 1) {
|
||||
/* Copy buffer since there could be a memory overlap when move
|
||||
* entity from tail to head in the same ziplist. */
|
||||
value = zmalloc(sz);
|
||||
memcpy(value, tmp, sz);
|
||||
} else {
|
||||
value = tmp;
|
||||
}
|
||||
|
||||
/* Add tail entry to head (must happen before tail is deleted). */
|
||||
@@ -1321,6 +1342,8 @@ void quicklistRotate(quicklist *quicklist) {
|
||||
|
||||
/* Remove tail entry. */
|
||||
quicklistDelIndex(quicklist, quicklist->tail, &p);
|
||||
if (value != (unsigned char*)longstr && value != tmp)
|
||||
zfree(value);
|
||||
}
|
||||
|
||||
/* pop from quicklist and return result in 'data' ptr. Value of 'data'
|
||||
@@ -1509,8 +1532,6 @@ void quicklistBookmarksClear(quicklist *ql) {
|
||||
|
||||
#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
|
||||
|
||||
#define OK printf("\tOK\n")
|
||||
|
||||
#define ERROR \
|
||||
do { \
|
||||
printf("\tERROR!\n"); \
|
||||
@@ -1630,7 +1651,6 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
|
||||
}
|
||||
|
||||
if (ql->len == 0 && !errors) {
|
||||
OK;
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -1679,8 +1699,6 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors)
|
||||
OK;
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -1692,9 +1710,10 @@ static char *genstr(char *prefix, int i) {
|
||||
}
|
||||
|
||||
/* main test, but callable from other files */
|
||||
int quicklistTest(int argc, char *argv[]) {
|
||||
int quicklistTest(int argc, char *argv[], int accurate) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(accurate);
|
||||
|
||||
unsigned int err = 0;
|
||||
int optimize_start =
|
||||
@@ -1703,11 +1722,14 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
printf("Starting optimization offset at: %d\n", optimize_start);
|
||||
|
||||
int options[] = {0, 1, 2, 3, 4, 5, 6, 10};
|
||||
int fills[] = {-5, -4, -3, -2, -1, 0,
|
||||
1, 2, 32, 66, 128, 999};
|
||||
size_t option_count = sizeof(options) / sizeof(*options);
|
||||
int fill_count = (int)(sizeof(fills) / sizeof(*fills));
|
||||
long long runtime[option_count];
|
||||
|
||||
for (int _i = 0; _i < (int)option_count; _i++) {
|
||||
printf("Testing Option %d\n", options[_i]);
|
||||
printf("Testing Compression option %d\n", options[_i]);
|
||||
long long start = mstime();
|
||||
|
||||
TEST("create list") {
|
||||
@@ -1732,57 +1754,53 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 32; f++) {
|
||||
TEST_DESC("add to tail 5x at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("add to tail 5x at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 5; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i), 32);
|
||||
if (ql->count != 5)
|
||||
ERROR;
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 1, 5, 5, 5);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 32; f++) {
|
||||
TEST_DESC("add to head 5x at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("add to head 5x at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 5; i++)
|
||||
quicklistPushHead(ql, genstr("hello", i), 32);
|
||||
if (ql->count != 5)
|
||||
ERROR;
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 1, 5, 5, 5);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 512; f++) {
|
||||
TEST_DESC("add to tail 500x at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("add to tail 500x at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i), 64);
|
||||
if (ql->count != 500)
|
||||
ERROR;
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 16, 500, 32, 20);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 512; f++) {
|
||||
TEST_DESC("add to head 500x at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("add to head 500x at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushHead(ql, genstr("hello", i), 32);
|
||||
if (ql->count != 500)
|
||||
ERROR;
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 16, 500, 20, 32);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
@@ -1795,9 +1813,9 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 32; f++) {
|
||||
TEST("rotate one val once") {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST("rotate one val once") {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
quicklistPushHead(ql, "hello", 6);
|
||||
quicklistRotate(ql);
|
||||
/* Ignore compression verify because ziplist is
|
||||
@@ -1807,10 +1825,9 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 3; f++) {
|
||||
TEST_DESC("rotate 500 val 5000 times at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("rotate 500 val 5000 times at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
quicklistPushHead(ql, "900", 3);
|
||||
quicklistPushHead(ql, "7000", 4);
|
||||
quicklistPushHead(ql, "-1200", 5);
|
||||
@@ -1822,11 +1839,11 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
ql_info(ql);
|
||||
quicklistRotate(ql);
|
||||
}
|
||||
if (f == 1)
|
||||
if (fills[f] == 1)
|
||||
ql_verify(ql, 504, 504, 1, 1);
|
||||
else if (f == 2)
|
||||
else if (fills[f] == 2)
|
||||
ql_verify(ql, 252, 504, 2, 2);
|
||||
else if (f == 32)
|
||||
else if (fills[f] == 32)
|
||||
ql_verify(ql, 16, 504, 32, 24);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
@@ -1847,7 +1864,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
unsigned int sz;
|
||||
long long lv;
|
||||
ql_info(ql);
|
||||
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
|
||||
assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv));
|
||||
assert(data != NULL);
|
||||
assert(sz == 32);
|
||||
if (strcmp(populate, (char *)data))
|
||||
@@ -1865,7 +1882,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
unsigned int sz;
|
||||
long long lv;
|
||||
ql_info(ql);
|
||||
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
|
||||
assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv));
|
||||
assert(data == NULL);
|
||||
assert(lv == 55513);
|
||||
ql_verify(ql, 0, 0, 0, 0);
|
||||
@@ -2003,11 +2020,10 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 12; f++) {
|
||||
TEST_DESC("insert once in elements while iterating at fill %d at "
|
||||
"compress %d\n",
|
||||
f, options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("insert once in elements while iterating at compress %d",
|
||||
options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
quicklistPushTail(ql, "abc", 3);
|
||||
quicklistSetFill(ql, 1);
|
||||
quicklistPushTail(ql, "def", 3); /* force to unique node */
|
||||
@@ -2059,12 +2075,10 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 1024; f++) {
|
||||
TEST_DESC(
|
||||
"insert [before] 250 new in middle of 500 elements at fill"
|
||||
" %d at compress %d",
|
||||
f, options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("insert [before] 250 new in middle of 500 elements at compress %d",
|
||||
options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i), 32);
|
||||
for (int i = 0; i < 250; i++) {
|
||||
@@ -2072,17 +2086,16 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
quicklistIndex(ql, 250, &entry);
|
||||
quicklistInsertBefore(ql, &entry, genstr("abc", i), 32);
|
||||
}
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 25, 750, 32, 20);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 1024; f++) {
|
||||
TEST_DESC("insert [after] 250 new in middle of 500 elements at "
|
||||
"fill %d at compress %d",
|
||||
f, options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("insert [after] 250 new in middle of 500 elements at compress %d",
|
||||
options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushHead(ql, genstr("hello", i), 32);
|
||||
for (int i = 0; i < 250; i++) {
|
||||
@@ -2094,7 +2107,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
if (ql->count != 750)
|
||||
ERR("List size not 750, but rather %ld", ql->count);
|
||||
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 26, 750, 20, 32);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
@@ -2132,70 +2145,58 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
quicklistRelease(copy);
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 512; f++) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i + 1), 32);
|
||||
quicklistEntry entry;
|
||||
quicklistIndex(ql, 1, &entry);
|
||||
if (!strcmp((char *)entry.value, "hello2"))
|
||||
OK;
|
||||
else
|
||||
if (strcmp((char *)entry.value, "hello2") != 0)
|
||||
ERR("Value: %s", entry.value);
|
||||
quicklistIndex(ql, 200, &entry);
|
||||
if (!strcmp((char *)entry.value, "hello201"))
|
||||
OK;
|
||||
else
|
||||
if (strcmp((char *)entry.value, "hello201") != 0)
|
||||
ERR("Value: %s", entry.value);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d",
|
||||
fills[f], options[_i]) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i + 1), 32);
|
||||
quicklistEntry entry;
|
||||
quicklistIndex(ql, -1, &entry);
|
||||
if (!strcmp((char *)entry.value, "hello500"))
|
||||
OK;
|
||||
else
|
||||
if (strcmp((char *)entry.value, "hello500") != 0)
|
||||
ERR("Value: %s", entry.value);
|
||||
quicklistIndex(ql, -2, &entry);
|
||||
if (!strcmp((char *)entry.value, "hello499"))
|
||||
OK;
|
||||
else
|
||||
if (strcmp((char *)entry.value, "hello499") != 0)
|
||||
ERR("Value: %s", entry.value);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST_DESC("index -100 from 500 list at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("index -100 from 500 list at fill %d at compress %d",
|
||||
fills[f], options[_i]) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i + 1), 32);
|
||||
quicklistEntry entry;
|
||||
quicklistIndex(ql, -100, &entry);
|
||||
if (!strcmp((char *)entry.value, "hello401"))
|
||||
OK;
|
||||
else
|
||||
if (strcmp((char *)entry.value, "hello401") != 0)
|
||||
ERR("Value: %s", entry.value);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST_DESC("index too big +1 from 50 list at fill %d at compress %d",
|
||||
f, options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
fills[f], options[_i]) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
for (int i = 0; i < 50; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i + 1), 32);
|
||||
quicklistEntry entry;
|
||||
if (quicklistIndex(ql, 50, &entry))
|
||||
ERR("Index found at 50 with 50 list: %.*s", entry.sz,
|
||||
entry.value);
|
||||
else
|
||||
OK;
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
@@ -2238,6 +2239,17 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST("delete less than fill but across nodes") {
|
||||
quicklist *ql = quicklistNew(-2, options[_i]);
|
||||
quicklistSetFill(ql, 32);
|
||||
for (int i = 0; i < 500; i++)
|
||||
quicklistPushTail(ql, genstr("hello", i + 1), 32);
|
||||
ql_verify(ql, 16, 500, 32, 20);
|
||||
quicklistDelRange(ql, 60, 10);
|
||||
ql_verify(ql, 16, 490, 32, 20);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST("delete negative 1 from 500 list") {
|
||||
quicklist *ql = quicklistNew(-2, options[_i]);
|
||||
quicklistSetFill(ql, 32);
|
||||
@@ -2356,12 +2368,11 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
quicklistReplaceAtIndex(ql, 1, "foo", 3);
|
||||
quicklistReplaceAtIndex(ql, -1, "bar", 3);
|
||||
quicklistRelease(ql);
|
||||
OK;
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 16; f++) {
|
||||
TEST_DESC("lrem test at fill %d at compress %d", f, options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("lrem test at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
char *words[] = {"abc", "foo", "bar", "foobar", "foobared",
|
||||
"zap", "bar", "test", "foo"};
|
||||
char *result[] = {"abc", "foo", "foobar", "foobared",
|
||||
@@ -2386,14 +2397,12 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
/* check result of lrem 0 bar */
|
||||
iter = quicklistGetIterator(ql, AL_START_HEAD);
|
||||
i = 0;
|
||||
int ok = 1;
|
||||
while (quicklistNext(iter, &entry)) {
|
||||
/* Result must be: abc, foo, foobar, foobared, zap, test,
|
||||
* foo */
|
||||
if (strncmp((char *)entry.value, result[i], entry.sz)) {
|
||||
ERR("No match at position %d, got %.*s instead of %s",
|
||||
i, entry.sz, entry.value, result[i]);
|
||||
ok = 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -2430,23 +2439,18 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
entry.sz)) {
|
||||
ERR("No match at position %d, got %.*s instead of %s",
|
||||
i, entry.sz, entry.value, resultB[resB - 1 - i]);
|
||||
ok = 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
quicklistReleaseIterator(iter);
|
||||
/* final result of all tests */
|
||||
if (ok)
|
||||
OK;
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 16; f++) {
|
||||
TEST_DESC("iterate reverse + delete at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("iterate reverse + delete at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
quicklistPushTail(ql, "abc", 3);
|
||||
quicklistPushTail(ql, "def", 3);
|
||||
quicklistPushTail(ql, "hij", 3);
|
||||
@@ -2483,10 +2487,9 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 800; f++) {
|
||||
TEST_DESC("iterator at index test at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("iterator at index test at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
char num[32];
|
||||
long long nums[5000];
|
||||
for (int i = 0; i < 760; i++) {
|
||||
@@ -2510,10 +2513,9 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 40; f++) {
|
||||
TEST_DESC("ltrim test A at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("ltrim test A at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
char num[32];
|
||||
long long nums[5000];
|
||||
for (int i = 0; i < 32; i++) {
|
||||
@@ -2521,7 +2523,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
int sz = ll2string(num, sizeof(num), nums[i]);
|
||||
quicklistPushTail(ql, num, sz);
|
||||
}
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 1, 32, 32, 32);
|
||||
/* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
|
||||
quicklistDelRange(ql, 0, 25);
|
||||
@@ -2534,18 +2536,17 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
"%lld",
|
||||
entry.longval, nums[25 + i]);
|
||||
}
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 1, 7, 7, 7);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 40; f++) {
|
||||
TEST_DESC("ltrim test B at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
TEST_DESC("ltrim test B at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
/* Force-disable compression because our 33 sequential
|
||||
* integers don't compress and the check always fails. */
|
||||
quicklist *ql = quicklistNew(f, QUICKLIST_NOCOMPRESS);
|
||||
quicklist *ql = quicklistNew(fills[f], QUICKLIST_NOCOMPRESS);
|
||||
char num[32];
|
||||
long long nums[5000];
|
||||
for (int i = 0; i < 33; i++) {
|
||||
@@ -2553,24 +2554,20 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
int sz = ll2string(num, sizeof(num), nums[i]);
|
||||
quicklistPushTail(ql, num, sz);
|
||||
}
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 2, 33, 32, 1);
|
||||
/* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */
|
||||
quicklistDelRange(ql, 0, 5);
|
||||
quicklistDelRange(ql, -16, 16);
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 1, 12, 12, 12);
|
||||
quicklistEntry entry;
|
||||
quicklistIndex(ql, 0, &entry);
|
||||
if (entry.longval != 5)
|
||||
ERR("A: longval not 5, but %lld", entry.longval);
|
||||
else
|
||||
OK;
|
||||
quicklistIndex(ql, -1, &entry);
|
||||
if (entry.longval != 16)
|
||||
ERR("B! got instead: %lld", entry.longval);
|
||||
else
|
||||
OK;
|
||||
quicklistPushTail(ql, "bobobob", 7);
|
||||
quicklistIndex(ql, -1, &entry);
|
||||
if (strncmp((char *)entry.value, "bobobob", 7))
|
||||
@@ -2587,10 +2584,9 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 40; f++) {
|
||||
TEST_DESC("ltrim test C at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("ltrim test C at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
char num[32];
|
||||
long long nums[5000];
|
||||
for (int i = 0; i < 33; i++) {
|
||||
@@ -2598,28 +2594,25 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
int sz = ll2string(num, sizeof(num), nums[i]);
|
||||
quicklistPushTail(ql, num, sz);
|
||||
}
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 2, 33, 32, 1);
|
||||
/* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
|
||||
quicklistDelRange(ql, 0, 3);
|
||||
quicklistDelRange(ql, -29,
|
||||
4000); /* make sure not loop forever */
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 1, 1, 1, 1);
|
||||
quicklistEntry entry;
|
||||
quicklistIndex(ql, 0, &entry);
|
||||
if (entry.longval != -5157318210846258173)
|
||||
ERROR;
|
||||
else
|
||||
OK;
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 40; f++) {
|
||||
TEST_DESC("ltrim test D at fill %d at compress %d", f,
|
||||
options[_i]) {
|
||||
quicklist *ql = quicklistNew(f, options[_i]);
|
||||
TEST_DESC("ltrim test D at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
quicklist *ql = quicklistNew(fills[f], options[_i]);
|
||||
char num[32];
|
||||
long long nums[5000];
|
||||
for (int i = 0; i < 33; i++) {
|
||||
@@ -2627,7 +2620,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
int sz = ll2string(num, sizeof(num), nums[i]);
|
||||
quicklistPushTail(ql, num, sz);
|
||||
}
|
||||
if (f == 32)
|
||||
if (fills[f] == 32)
|
||||
ql_verify(ql, 2, 33, 32, 1);
|
||||
quicklistDelRange(ql, -12, 3);
|
||||
if (ql->count != 30)
|
||||
@@ -2637,9 +2630,8 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int f = optimize_start; f < 72; f++) {
|
||||
TEST_DESC("create quicklist from ziplist at fill %d at compress %d",
|
||||
f, options[_i]) {
|
||||
TEST_DESC("create quicklist from ziplist at compress %d", options[_i]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
unsigned char *zl = ziplistNew();
|
||||
long long nums[64];
|
||||
char num[64];
|
||||
@@ -2653,12 +2645,12 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
zl = ziplistPush(zl, (unsigned char *)genstr("hello", i),
|
||||
32, ZIPLIST_TAIL);
|
||||
}
|
||||
quicklist *ql = quicklistCreateFromZiplist(f, options[_i], zl);
|
||||
if (f == 1)
|
||||
quicklist *ql = quicklistCreateFromZiplist(fills[f], options[_i], zl);
|
||||
if (fills[f] == 1)
|
||||
ql_verify(ql, 66, 66, 1, 1);
|
||||
else if (f == 32)
|
||||
else if (fills[f] == 32)
|
||||
ql_verify(ql, 3, 66, 32, 2);
|
||||
else if (f == 66)
|
||||
else if (fills[f] == 66)
|
||||
ql_verify(ql, 1, 66, 66, 66);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
@@ -2671,45 +2663,58 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
/* Run a longer test of compression depth outside of primary test loop. */
|
||||
int list_sizes[] = {250, 251, 500, 999, 1000};
|
||||
long long start = mstime();
|
||||
for (int list = 0; list < (int)(sizeof(list_sizes) / sizeof(*list_sizes));
|
||||
list++) {
|
||||
for (int f = optimize_start; f < 128; f++) {
|
||||
for (int depth = 1; depth < 40; depth++) {
|
||||
/* skip over many redundant test cases */
|
||||
TEST_DESC("verify specific compression of interior nodes with "
|
||||
"%d list "
|
||||
"at fill %d at compress %d",
|
||||
list_sizes[list], f, depth) {
|
||||
quicklist *ql = quicklistNew(f, depth);
|
||||
int list_count = accurate ? (int)(sizeof(list_sizes) / sizeof(*list_sizes)) : 1;
|
||||
for (int list = 0; list < list_count; list++) {
|
||||
TEST_DESC("verify specific compression of interior nodes with %d list ",
|
||||
list_sizes[list]) {
|
||||
for (int f = 0; f < fill_count; f++) {
|
||||
for (int depth = 1; depth < 40; depth++) {
|
||||
/* skip over many redundant test cases */
|
||||
quicklist *ql = quicklistNew(fills[f], depth);
|
||||
for (int i = 0; i < list_sizes[list]; i++) {
|
||||
quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64);
|
||||
quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64);
|
||||
}
|
||||
|
||||
quicklistNode *node = ql->head;
|
||||
unsigned int low_raw = ql->compress;
|
||||
unsigned int high_raw = ql->len - ql->compress;
|
||||
|
||||
for (unsigned int at = 0; at < ql->len;
|
||||
at++, node = node->next) {
|
||||
if (at < low_raw || at >= high_raw) {
|
||||
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
|
||||
ERR("Incorrect compression: node %d is "
|
||||
"compressed at depth %d ((%u, %u); total "
|
||||
"nodes: %lu; size: %u)",
|
||||
at, depth, low_raw, high_raw, ql->len,
|
||||
node->sz);
|
||||
for (int step = 0; step < 2; step++) {
|
||||
/* test remove node */
|
||||
if (step == 1) {
|
||||
for (int i = 0; i < list_sizes[list] / 2; i++) {
|
||||
unsigned char *data;
|
||||
assert(quicklistPop(ql, QUICKLIST_HEAD, &data,
|
||||
NULL, NULL));
|
||||
zfree(data);
|
||||
assert(quicklistPop(ql, QUICKLIST_TAIL, &data,
|
||||
NULL, NULL));
|
||||
zfree(data);
|
||||
}
|
||||
} else {
|
||||
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
|
||||
ERR("Incorrect non-compression: node %d is NOT "
|
||||
"compressed at depth %d ((%u, %u); total "
|
||||
"nodes: %lu; size: %u; attempted: %d)",
|
||||
at, depth, low_raw, high_raw, ql->len,
|
||||
node->sz, node->attempted_compress);
|
||||
}
|
||||
quicklistNode *node = ql->head;
|
||||
unsigned int low_raw = ql->compress;
|
||||
unsigned int high_raw = ql->len - ql->compress;
|
||||
|
||||
for (unsigned int at = 0; at < ql->len;
|
||||
at++, node = node->next) {
|
||||
if (at < low_raw || at >= high_raw) {
|
||||
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
|
||||
ERR("Incorrect compression: node %d is "
|
||||
"compressed at depth %d ((%u, %u); total "
|
||||
"nodes: %lu; size: %u)",
|
||||
at, depth, low_raw, high_raw, ql->len,
|
||||
node->sz);
|
||||
}
|
||||
} else {
|
||||
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
|
||||
ERR("Incorrect non-compression: node %d is NOT "
|
||||
"compressed at depth %d ((%u, %u); total "
|
||||
"nodes: %lu; size: %u; attempted: %d)",
|
||||
at, depth, low_raw, high_raw, ql->len,
|
||||
node->sz, node->attempted_compress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -199,7 +199,7 @@ quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name);
|
||||
void quicklistBookmarksClear(quicklist *ql);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int quicklistTest(int argc, char *argv[]);
|
||||
int quicklistTest(int argc, char *argv[], int accurate);
|
||||
#endif
|
||||
|
||||
/* Directions for iterators */
|
||||
|
||||
@@ -1270,6 +1270,7 @@ void raxStart(raxIterator *it, rax *rt) {
|
||||
* is a low level function used to implement the iterator, not callable by
|
||||
* the user. Returns 0 on out of memory, otherwise 1 is returned. */
|
||||
int raxIteratorAddChars(raxIterator *it, unsigned char *s, size_t len) {
|
||||
if (len == 0) return 1;
|
||||
if (it->key_max < it->key_len+len) {
|
||||
unsigned char *old = (it->key == it->key_static_string) ? NULL :
|
||||
it->key;
|
||||
|
||||
@@ -288,12 +288,16 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
} else if (enctype == RDB_ENC_INT16) {
|
||||
uint16_t v;
|
||||
if (rioRead(rdb,enc,2) == 0) return NULL;
|
||||
v = enc[0]|(enc[1]<<8);
|
||||
v = ((uint32_t)enc[0])|
|
||||
((uint32_t)enc[1]<<8);
|
||||
val = (int16_t)v;
|
||||
} else if (enctype == RDB_ENC_INT32) {
|
||||
uint32_t v;
|
||||
if (rioRead(rdb,enc,4) == 0) return NULL;
|
||||
v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24);
|
||||
v = ((uint32_t)enc[0])|
|
||||
((uint32_t)enc[1]<<8)|
|
||||
((uint32_t)enc[2]<<16)|
|
||||
((uint32_t)enc[3]<<24);
|
||||
val = (int32_t)v;
|
||||
} else {
|
||||
rdbReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);
|
||||
@@ -545,8 +549,12 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
|
||||
}
|
||||
return buf;
|
||||
} else {
|
||||
robj *o = encode ? createStringObject(SDS_NOINIT,len) :
|
||||
createRawStringObject(SDS_NOINIT,len);
|
||||
robj *o = encode ? tryCreateStringObject(SDS_NOINIT,len) :
|
||||
tryCreateRawStringObject(SDS_NOINIT,len);
|
||||
if (!o) {
|
||||
serverLog(server.loading? LL_WARNING: LL_VERBOSE, "rdbGenericLoadStringObject failed allocating %llu bytes", len);
|
||||
return NULL;
|
||||
}
|
||||
if (len && rioRead(rdb,o->ptr,len) == 0) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
@@ -1073,8 +1081,7 @@ size_t rdbSavedObjectLen(robj *o, robj *key) {
|
||||
|
||||
/* Save a key-value pair, with expire time, type, key, value.
|
||||
* On error -1 is returned.
|
||||
* On success if the key was actually saved 1 is returned, otherwise 0
|
||||
* is returned (the key was already expired). */
|
||||
* On success if the key was actually saved 1 is returned. */
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
|
||||
int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU;
|
||||
int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;
|
||||
@@ -1517,12 +1524,17 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
}
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
* On success a newly allocated object is returned, otherwise NULL.
|
||||
* When the function returns NULL and if 'error' is not NULL, the
|
||||
* integer pointed by 'error' is set to the type of error that occurred */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int *error) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
|
||||
/* Set default error of load object, it will be set to 0 on success. */
|
||||
if (error) *error = RDB_LOAD_ERR_OTHER;
|
||||
|
||||
int deep_integrity_validation = server.sanitize_dump_payload == SANITIZE_DUMP_YES;
|
||||
if (server.sanitize_dump_payload == SANITIZE_DUMP_CLIENTS) {
|
||||
/* Skip sanitization when loading (an RDB), or getting a RESTORE command
|
||||
@@ -1541,6 +1553,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
} else if (rdbtype == RDB_TYPE_LIST) {
|
||||
/* Read list value */
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
o = createQuicklistObject();
|
||||
quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
|
||||
@@ -1561,9 +1574,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
} else if (rdbtype == RDB_TYPE_SET) {
|
||||
/* Read Set value */
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
/* Use a regular set when there are too many entries. */
|
||||
if (len > server.set_max_intset_entries) {
|
||||
size_t max_entries = server.set_max_intset_entries;
|
||||
if (max_entries >= 1<<30) max_entries = 1<<30;
|
||||
if (len > max_entries) {
|
||||
o = createSetObject();
|
||||
/* It's faster to expand the dict to the right size asap in order
|
||||
* to avoid rehashing */
|
||||
@@ -1624,10 +1640,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
} else if (rdbtype == RDB_TYPE_ZSET_2 || rdbtype == RDB_TYPE_ZSET) {
|
||||
/* Read list/set value. */
|
||||
uint64_t zsetlen;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
zset *zs;
|
||||
|
||||
if ((zsetlen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (zsetlen == 0) goto emptykey;
|
||||
|
||||
o = createZsetObject();
|
||||
zs = o->ptr;
|
||||
|
||||
@@ -1664,6 +1682,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
/* Don't care about integer-encoded strings. */
|
||||
if (sdslen(sdsele) > maxelelen) maxelelen = sdslen(sdsele);
|
||||
totelelen += sdslen(sdsele);
|
||||
|
||||
znode = zslInsert(zs->zsl,score,sdsele);
|
||||
if (dictAdd(zs->dict,sdsele,&znode->score) != DICT_OK) {
|
||||
@@ -1676,8 +1695,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
/* Convert *after* loading, since sorted sets are not stored ordered. */
|
||||
if (zsetLength(o) <= server.zset_max_ziplist_entries &&
|
||||
maxelelen <= server.zset_max_ziplist_value)
|
||||
zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
maxelelen <= server.zset_max_ziplist_value &&
|
||||
ziplistSafeToAdd(NULL, totelelen))
|
||||
{
|
||||
zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_HASH) {
|
||||
uint64_t len;
|
||||
int ret;
|
||||
@@ -1686,6 +1708,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
|
||||
len = rdbLoadLen(rdb, NULL);
|
||||
if (len == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
o = createHashObject();
|
||||
|
||||
@@ -1730,21 +1753,30 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert to hash table if size threshold is exceeded */
|
||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
||||
sdslen(value) > server.hash_max_ziplist_value ||
|
||||
!ziplistSafeToAdd(o->ptr, sdslen(field)+sdslen(value)))
|
||||
{
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
ret = dictAdd((dict*)o->ptr, field, value);
|
||||
if (ret == DICT_ERR) {
|
||||
rdbReportCorruptRDB("Duplicate hash fields detected");
|
||||
if (dupSearchDict) dictRelease(dupSearchDict);
|
||||
sdsfree(value);
|
||||
sdsfree(field);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Add pair to ziplist */
|
||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
|
||||
sdslen(field), ZIPLIST_TAIL);
|
||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
|
||||
sdslen(value), ZIPLIST_TAIL);
|
||||
|
||||
/* Convert to hash table if size threshold is exceeded */
|
||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
||||
sdslen(value) > server.hash_max_ziplist_value)
|
||||
{
|
||||
sdsfree(field);
|
||||
sdsfree(value);
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
}
|
||||
sdsfree(field);
|
||||
sdsfree(value);
|
||||
}
|
||||
@@ -1793,6 +1825,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
serverAssert(len == 0);
|
||||
} else if (rdbtype == RDB_TYPE_LIST_QUICKLIST) {
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
if (len == 0) goto emptykey;
|
||||
|
||||
o = createQuicklistObject();
|
||||
quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
|
||||
server.list_compress_depth);
|
||||
@@ -1812,7 +1846,19 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
zfree(zl);
|
||||
return NULL;
|
||||
}
|
||||
quicklistAppendZiplist(o->ptr, zl);
|
||||
|
||||
/* Silently skip empty ziplists, if we'll end up with empty quicklist we'll fail later. */
|
||||
if (ziplistLen(zl) == 0) {
|
||||
zfree(zl);
|
||||
continue;
|
||||
} else {
|
||||
quicklistAppendZiplist(o->ptr, zl);
|
||||
}
|
||||
}
|
||||
|
||||
if (quicklistCount(o->ptr) == 0) {
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_HASH_ZIPMAP ||
|
||||
rdbtype == RDB_TYPE_LIST_ZIPLIST ||
|
||||
@@ -1857,12 +1903,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
|
||||
if (flen > maxlen) maxlen = flen;
|
||||
if (vlen > maxlen) maxlen = vlen;
|
||||
zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
|
||||
zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
|
||||
|
||||
/* search for duplicate records */
|
||||
sds field = sdstrynewlen(fstr, flen);
|
||||
if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK) {
|
||||
if (!field || dictAdd(dupSearchDict, field, NULL) != DICT_OK ||
|
||||
!ziplistSafeToAdd(zl, (size_t)flen + vlen)) {
|
||||
rdbReportCorruptRDB("Hash zipmap with dup elements, or big length (%u)", flen);
|
||||
dictRelease(dupSearchDict);
|
||||
sdsfree(field);
|
||||
@@ -1871,6 +1916,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
|
||||
zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
|
||||
}
|
||||
|
||||
dictRelease(dupSearchDict);
|
||||
@@ -1895,6 +1943,14 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ziplistLen(encoded) == 0) {
|
||||
zfree(encoded);
|
||||
o->ptr = NULL;
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
|
||||
o->type = OBJ_LIST;
|
||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
||||
listTypeConvert(o,OBJ_ENCODING_QUICKLIST);
|
||||
@@ -1924,6 +1980,13 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
o->type = OBJ_ZSET;
|
||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
||||
if (zsetLength(o) == 0) {
|
||||
zfree(encoded);
|
||||
o->ptr = NULL;
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
|
||||
if (zsetLength(o) > server.zset_max_ziplist_entries)
|
||||
zsetConvert(o,OBJ_ENCODING_SKIPLIST);
|
||||
break;
|
||||
@@ -1938,6 +2001,13 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
o->type = OBJ_HASH;
|
||||
o->encoding = OBJ_ENCODING_ZIPLIST;
|
||||
if (hashTypeLength(o) == 0) {
|
||||
zfree(encoded);
|
||||
o->ptr = NULL;
|
||||
decrRefCount(o);
|
||||
goto emptykey;
|
||||
}
|
||||
|
||||
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
@@ -2006,7 +2076,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
|
||||
/* Insert the key in the radix tree. */
|
||||
int retval = raxInsert(s->rax,
|
||||
int retval = raxTryInsert(s->rax,
|
||||
(unsigned char*)nodekey,sizeof(streamID),lp,NULL);
|
||||
sdsfree(nodekey);
|
||||
if (!retval) {
|
||||
@@ -2095,7 +2165,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
streamFreeNACK(nack);
|
||||
return NULL;
|
||||
}
|
||||
if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
if (!raxTryInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
rdbReportCorruptRDB("Duplicated global PEL entry "
|
||||
"loading stream consumer group");
|
||||
decrRefCount(o);
|
||||
@@ -2159,15 +2229,33 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
* loading the global PEL. Then set the same shared
|
||||
* NACK structure also in the consumer-specific PEL. */
|
||||
nack->consumer = consumer;
|
||||
if (!raxInsert(consumer->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
if (!raxTryInsert(consumer->pel,rawid,sizeof(rawid),nack,NULL)) {
|
||||
rdbReportCorruptRDB("Duplicated consumer PEL entry "
|
||||
" loading a stream consumer "
|
||||
"group");
|
||||
decrRefCount(o);
|
||||
streamFreeNACK(nack);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify that each PEL eventually got a consumer assigned to it. */
|
||||
if (deep_integrity_validation) {
|
||||
raxIterator ri_cg_pel;
|
||||
raxStart(&ri_cg_pel,cgroup->pel);
|
||||
raxSeek(&ri_cg_pel,"^",NULL,0);
|
||||
while(raxNext(&ri_cg_pel)) {
|
||||
streamNACK *nack = ri_cg_pel.data;
|
||||
if (!nack->consumer) {
|
||||
raxStop(&ri_cg_pel);
|
||||
rdbReportCorruptRDB("Stream CG PEL entry without consumer");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
raxStop(&ri_cg_pel);
|
||||
}
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
@@ -2176,16 +2264,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
return NULL;
|
||||
}
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
|
||||
if (rdbCheckMode && rdbtype == RDB_TYPE_MODULE_2) {
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
return rdbLoadCheckModuleValue(rdb,name);
|
||||
}
|
||||
|
||||
if (mt == NULL) {
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
rdbReportCorruptRDB("The RDB file contains module data I can't load: no matching module '%s'", name);
|
||||
rdbReportCorruptRDB("The RDB file contains module data I can't load: no matching module type '%s'", name);
|
||||
return NULL;
|
||||
}
|
||||
RedisModuleIO io;
|
||||
@@ -2212,7 +2301,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
return NULL;
|
||||
}
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
rdbReportCorruptRDB("The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
rdbReportCorruptRDB("The RDB file contains module data for the module '%s' that is not terminated by "
|
||||
"the proper module value EOF marker", moduleTypeModuleName(mt));
|
||||
if (ptr) {
|
||||
o = createModuleObject(mt,ptr); /* creating just in order to easily destroy */
|
||||
decrRefCount(o);
|
||||
@@ -2222,8 +2312,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
}
|
||||
|
||||
if (ptr == NULL) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
rdbReportCorruptRDB("The RDB file contains module data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
rdbReportCorruptRDB("The RDB file contains module data for the module type '%s', that the responsible "
|
||||
"module is not able to load. Check for modules log above for additional clues.",
|
||||
moduleTypeModuleName(mt));
|
||||
return NULL;
|
||||
}
|
||||
o = createModuleObject(mt,ptr);
|
||||
@@ -2231,7 +2322,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
|
||||
return NULL;
|
||||
}
|
||||
if (error) *error = 0;
|
||||
return o;
|
||||
|
||||
emptykey:
|
||||
if (error) *error = RDB_LOAD_ERR_EMPTY_KEY;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark that we are loading in the global state and setup the fields
|
||||
@@ -2332,6 +2428,8 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
int type, rdbver;
|
||||
redisDb *db = server.db+0;
|
||||
char buf[1024];
|
||||
int error;
|
||||
long long empty_keys_skipped = 0, expired_keys_skipped = 0, keys_loaded = 0;
|
||||
|
||||
rdb->update_cksum = rdbLoadProgressCallback;
|
||||
rdb->max_processing_chunk = server.loading_process_events_interval_bytes;
|
||||
@@ -2421,7 +2519,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* An AUX field is composed of two strings: key and value. */
|
||||
robj *auxkey, *auxval;
|
||||
if ((auxkey = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(rdb)) == NULL) {
|
||||
decrRefCount(auxkey);
|
||||
goto eoferr;
|
||||
}
|
||||
|
||||
if (((char*)auxkey->ptr)[0] == '%') {
|
||||
/* All the fields with a name staring with '%' are considered
|
||||
@@ -2482,8 +2583,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
int when_opcode = rdbLoadLen(rdb,NULL);
|
||||
int when = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) goto eoferr;
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT)
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT) {
|
||||
rdbReportReadError("bad when_opcode");
|
||||
goto eoferr;
|
||||
}
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
@@ -2504,19 +2607,20 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
io.ver = 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
if (mt->aux_load(&io,moduleid&1023, when) != REDISMODULE_OK || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
exit(1);
|
||||
}
|
||||
int rc = mt->aux_load(&io,moduleid&1023, when);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
if (rc != REDISMODULE_OK || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
goto eoferr;
|
||||
}
|
||||
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
exit(1);
|
||||
goto eoferr;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
@@ -2531,10 +2635,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if ((key = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) {
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
}
|
||||
val = rdbLoadObject(type,rdb,key,&error);
|
||||
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
@@ -2544,18 +2645,33 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* Similarly if the RDB is the preamble of an AOF file, we want to
|
||||
* load all the keys as they are, since the log of operations later
|
||||
* assume to work in an exact keyspace state. */
|
||||
if (iAmMaster() &&
|
||||
if (val == NULL) {
|
||||
/* Since we used to have bug that could lead to empty keys
|
||||
* (See #8453), we rather not fail when empty key is encountered
|
||||
* in an RDB file, instead we will silently discard it and
|
||||
* continue loading. */
|
||||
if (error == RDB_LOAD_ERR_EMPTY_KEY) {
|
||||
if(empty_keys_skipped++ < 10)
|
||||
serverLog(LL_WARNING, "rdbLoadObject skipping empty key: %s", key);
|
||||
sdsfree(key);
|
||||
} else {
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
}
|
||||
} else if (iAmMaster() &&
|
||||
!(rdbflags&RDBFLAGS_AOF_PREAMBLE) &&
|
||||
expiretime != -1 && expiretime < now)
|
||||
{
|
||||
sdsfree(key);
|
||||
decrRefCount(val);
|
||||
expired_keys_skipped++;
|
||||
} else {
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
|
||||
/* Add the new object in the hash table */
|
||||
int added = dbAddRDBLoad(db,key,val);
|
||||
keys_loaded++;
|
||||
if (!added) {
|
||||
if (rdbflags & RDBFLAGS_ALLOW_DUP) {
|
||||
/* This flag is useful for DEBUG RELOAD special modes.
|
||||
@@ -2612,6 +2728,16 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty_keys_skipped) {
|
||||
serverLog(LL_WARNING,
|
||||
"Done loading RDB, keys loaded: %lld, keys expired: %lld, empty keys skipped: %lld.",
|
||||
keys_loaded, expired_keys_skipped, empty_keys_skipped);
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"Done loading RDB, keys loaded: %lld, keys expired: %lld.",
|
||||
keys_loaded, expired_keys_skipped);
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
/* Unexpected end of file is handled here calling rdbReportReadError():
|
||||
@@ -2689,6 +2815,7 @@ static void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
}
|
||||
if (server.rdb_child_exit_pipe!=-1)
|
||||
close(server.rdb_child_exit_pipe);
|
||||
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
|
||||
close(server.rdb_pipe_read);
|
||||
server.rdb_child_exit_pipe = -1;
|
||||
server.rdb_pipe_read = -1;
|
||||
@@ -2729,7 +2856,7 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
* the cleanup needed. */
|
||||
void killRDBChild(void) {
|
||||
kill(server.child_pid, SIGUSR1);
|
||||
/* Because we are not using here wait4 (like we have in killAppendOnlyChild
|
||||
/* Because we are not using here waitpid (like we have in killAppendOnlyChild
|
||||
* and TerminateModuleForkChild), all the cleanup operations is done by
|
||||
* checkChildrenDone, that later will find that the process killed.
|
||||
* This includes:
|
||||
@@ -2792,6 +2919,10 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
|
||||
rioInitWithFd(&rdb,rdb_pipe_write);
|
||||
|
||||
/* Close the reading part, so that if the parent crashes, the child will
|
||||
* get a write error and exit. */
|
||||
close(server.rdb_pipe_read);
|
||||
|
||||
redisSetProcTitle("redis-rdb-to-slaves");
|
||||
redisSetCpuAffinity(server.bgsave_cpulist);
|
||||
|
||||
|
||||
@@ -127,6 +127,11 @@
|
||||
#define RDBFLAGS_REPLICATION (1<<1) /* Load/save for SYNC. */
|
||||
#define RDBFLAGS_ALLOW_DUP (1<<2) /* Allow duplicated keys when loading.*/
|
||||
|
||||
/* When rdbLoadObject() returns NULL, the err flag is
|
||||
* set to hold the type of error that occurred */
|
||||
#define RDB_LOAD_ERR_EMPTY_KEY 1 /* Error of empty key */
|
||||
#define RDB_LOAD_ERR_OTHER 2 /* Any other errors */
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
int rdbLoadType(rio *rdb);
|
||||
int rdbSaveTime(rio *rdb, time_t t);
|
||||
@@ -145,7 +150,7 @@ void rdbRemoveTempFile(pid_t childpid, int from_signal);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key, int *error);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
|
||||
+53
-46
@@ -99,10 +99,8 @@ static struct config {
|
||||
int randomkeys_keyspacelen;
|
||||
int keepalive;
|
||||
int pipeline;
|
||||
int showerrors;
|
||||
long long start;
|
||||
long long totlatency;
|
||||
long long *latency;
|
||||
const char *title;
|
||||
list *clients;
|
||||
int quiet;
|
||||
@@ -308,7 +306,9 @@ static redisContext *getRedisContext(const char *ip, int port,
|
||||
fprintf(stderr, "Node %s:%d replied with error:\n%s\n", ip, port, reply->str);
|
||||
else
|
||||
fprintf(stderr, "Node %s replied with error:\n%s\n", hostsocket, reply->str);
|
||||
goto cleanup;
|
||||
freeReplyObject(reply);
|
||||
redisFree(ctx);
|
||||
exit(1);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return ctx;
|
||||
@@ -367,9 +367,16 @@ fail:
|
||||
fprintf(stderr, "ERROR: failed to fetch CONFIG from ");
|
||||
if (hostsocket == NULL) fprintf(stderr, "%s:%d\n", ip, port);
|
||||
else fprintf(stderr, "%s\n", hostsocket);
|
||||
int abort_test = 0;
|
||||
if (reply && reply->type == REDIS_REPLY_ERROR &&
|
||||
(!strncmp(reply->str,"NOAUTH",5) ||
|
||||
!strncmp(reply->str,"WRONGPASS",9) ||
|
||||
!strncmp(reply->str,"NOPERM",5)))
|
||||
abort_test = 1;
|
||||
freeReplyObject(reply);
|
||||
redisFree(c);
|
||||
freeRedisConfig(cfg);
|
||||
if (abort_test) exit(1);
|
||||
return NULL;
|
||||
}
|
||||
static void freeRedisConfig(redisConfig *cfg) {
|
||||
@@ -514,44 +521,39 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
exit(1);
|
||||
}
|
||||
redisReply *r = reply;
|
||||
int is_err = (r->type == REDIS_REPLY_ERROR);
|
||||
|
||||
if (is_err && config.showerrors) {
|
||||
/* TODO: static lasterr_time not thread-safe */
|
||||
static time_t lasterr_time = 0;
|
||||
time_t now = time(NULL);
|
||||
if (lasterr_time != now) {
|
||||
lasterr_time = now;
|
||||
if (c->cluster_node) {
|
||||
printf("Error from server %s:%d: %s\n",
|
||||
if (r->type == REDIS_REPLY_ERROR) {
|
||||
/* Try to update slots configuration if reply error is
|
||||
* MOVED/ASK/CLUSTERDOWN and the key(s) used by the command
|
||||
* contain(s) the slot hash tag.
|
||||
* If the error is not topology-update related then we
|
||||
* immediately exit to avoid false results. */
|
||||
if (c->cluster_node && c->staglen) {
|
||||
int fetch_slots = 0, do_wait = 0;
|
||||
if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3))
|
||||
fetch_slots = 1;
|
||||
else if (!strncmp(r->str,"CLUSTERDOWN",11)) {
|
||||
/* Usually the cluster is able to recover itself after
|
||||
* a CLUSTERDOWN error, so try to sleep one second
|
||||
* before requesting the new configuration. */
|
||||
fetch_slots = 1;
|
||||
do_wait = 1;
|
||||
printf("Error from server %s:%d: %s.\n",
|
||||
c->cluster_node->ip,
|
||||
c->cluster_node->port,
|
||||
r->str);
|
||||
}
|
||||
if (do_wait) sleep(1);
|
||||
if (fetch_slots && !fetchClusterSlotsConfiguration(c))
|
||||
exit(1);
|
||||
} else {
|
||||
if (c->cluster_node) {
|
||||
printf("Error from server %s:%d: %s\n",
|
||||
c->cluster_node->ip,
|
||||
c->cluster_node->port,
|
||||
r->str);
|
||||
} else printf("Error from server: %s\n", r->str);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to update slots configuration if reply error is
|
||||
* MOVED/ASK/CLUSTERDOWN and the key(s) used by the command
|
||||
* contain(s) the slot hash tag. */
|
||||
if (is_err && c->cluster_node && c->staglen) {
|
||||
int fetch_slots = 0, do_wait = 0;
|
||||
if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3))
|
||||
fetch_slots = 1;
|
||||
else if (!strncmp(r->str,"CLUSTERDOWN",11)) {
|
||||
/* Usually the cluster is able to recover itself after
|
||||
* a CLUSTERDOWN error, so try to sleep one second
|
||||
* before requesting the new configuration. */
|
||||
fetch_slots = 1;
|
||||
do_wait = 1;
|
||||
printf("Error from server %s:%d: %s\n",
|
||||
c->cluster_node->ip,
|
||||
c->cluster_node->port,
|
||||
r->str);
|
||||
}
|
||||
if (do_wait) sleep(1);
|
||||
if (fetch_slots && !fetchClusterSlotsConfiguration(c))
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
@@ -641,6 +643,9 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
fprintf(stderr, "Error writing to the server: %s\n", strerror(errno));
|
||||
freeClient(c);
|
||||
return;
|
||||
} else if (nwritten > 0) {
|
||||
c->written += nwritten;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE);
|
||||
@@ -1294,8 +1299,7 @@ static int fetchClusterSlotsConfiguration(client c) {
|
||||
atomicGetIncr(config.is_fetching_slots, is_fetching_slots, 1);
|
||||
if (is_fetching_slots) return -1; //TODO: use other codes || errno ?
|
||||
atomicSet(config.is_fetching_slots, 1);
|
||||
if (config.showerrors)
|
||||
printf("Cluster slots configuration changed, fetching new one...\n");
|
||||
printf("WARNING: Cluster slots configuration changed, fetching new one...\n");
|
||||
const char *errmsg = "Failed to update cluster slots configuration";
|
||||
static dictType dtype = {
|
||||
dictSdsHash, /* hash function */
|
||||
@@ -1471,7 +1475,8 @@ int parseOptions(int argc, const char **argv) {
|
||||
} else if (!strcmp(argv[i],"-I")) {
|
||||
config.idlemode = 1;
|
||||
} else if (!strcmp(argv[i],"-e")) {
|
||||
config.showerrors = 1;
|
||||
printf("WARNING: -e option has been deprecated. "
|
||||
"We now immediatly exit on error to avoid false results.\n");
|
||||
} else if (!strcmp(argv[i],"-t")) {
|
||||
if (lastarg) goto invalid;
|
||||
/* We get the list of tests to run as a string in the form
|
||||
@@ -1574,8 +1579,6 @@ usage:
|
||||
" is executed. Default tests use this to hit random keys in the\n"
|
||||
" specified range.\n"
|
||||
" -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).\n"
|
||||
" -e If server replies with errors, show them on stdout.\n"
|
||||
" (no more than 1 error per second is displayed)\n"
|
||||
" -q Quiet. Just show query/sec values\n"
|
||||
" --precision Number of decimal places to display in latency output (default 0)\n"
|
||||
" --csv Output in CSV format\n"
|
||||
@@ -1656,7 +1659,10 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
const float instantaneous_rps = (float)(requests_finished-previous_requests_finished)/instantaneous_dt;
|
||||
config.previous_tick = current_tick;
|
||||
atomicSet(config.previous_requests_finished,requests_finished);
|
||||
config.last_printed_bytes = printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)\r", config.title, instantaneous_rps, rps, hdr_mean(config.current_sec_latency_histogram)/1000.0f, hdr_mean(config.latency_histogram)/1000.0f);
|
||||
int printed_bytes = printf("%s: rps=%.1f (overall: %.1f) avg_msec=%.3f (overall: %.3f)\r", config.title, instantaneous_rps, rps, hdr_mean(config.current_sec_latency_histogram)/1000.0f, hdr_mean(config.latency_histogram)/1000.0f);
|
||||
if (printed_bytes > config.last_printed_bytes){
|
||||
config.last_printed_bytes = printed_bytes;
|
||||
}
|
||||
hdr_reset(config.current_sec_latency_histogram);
|
||||
fflush(stdout);
|
||||
return 250; /* every 250ms */
|
||||
@@ -1697,7 +1703,6 @@ int main(int argc, const char **argv) {
|
||||
config.keepalive = 1;
|
||||
config.datasize = 3;
|
||||
config.pipeline = 1;
|
||||
config.showerrors = 0;
|
||||
config.randomkeys = 0;
|
||||
config.randomkeys_keyspacelen = 0;
|
||||
config.quiet = 0;
|
||||
@@ -1780,8 +1785,9 @@ int main(int argc, const char **argv) {
|
||||
} else {
|
||||
config.redis_config =
|
||||
getRedisConfig(config.hostip, config.hostport, config.hostsocket);
|
||||
if (config.redis_config == NULL)
|
||||
if (config.redis_config == NULL) {
|
||||
fprintf(stderr, "WARN: could not fetch server CONFIG\n");
|
||||
}
|
||||
}
|
||||
if (config.num_threads > 0) {
|
||||
pthread_mutex_init(&(config.liveclients_mutex), NULL);
|
||||
@@ -1944,8 +1950,8 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
|
||||
if (test_is_selected("lrange") || test_is_selected("lrange_500")) {
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 449",tag);
|
||||
benchmark("LRANGE_500 (first 450 elements)",cmd,len);
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 499",tag);
|
||||
benchmark("LRANGE_500 (first 500 elements)",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
@@ -1972,6 +1978,7 @@ int main(int argc, const char **argv) {
|
||||
if (!config.csv) printf("\n");
|
||||
} while(config.loop);
|
||||
|
||||
zfree(data);
|
||||
if (config.redis_config != NULL) freeRedisConfig(config.redis_config);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -39,12 +39,14 @@
|
||||
|
||||
static char error[1044];
|
||||
static off_t epos;
|
||||
static long long line = 1;
|
||||
|
||||
int consumeNewline(char *buf) {
|
||||
if (strncmp(buf,"\r\n",2) != 0) {
|
||||
ERROR("Expected \\r\\n, got: %02x%02x",buf[0],buf[1]);
|
||||
return 0;
|
||||
}
|
||||
line += 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -201,8 +203,8 @@ int redis_check_aof_main(int argc, char **argv) {
|
||||
|
||||
off_t pos = process(fp);
|
||||
off_t diff = size-pos;
|
||||
printf("AOF analyzed: size=%lld, ok_up_to=%lld, diff=%lld\n",
|
||||
(long long) size, (long long) pos, (long long) diff);
|
||||
printf("AOF analyzed: size=%lld, ok_up_to=%lld, ok_up_to_line=%lld, diff=%lld\n",
|
||||
(long long) size, (long long) pos, line, (long long) diff);
|
||||
if (diff > 0) {
|
||||
if (fix) {
|
||||
char buf[2];
|
||||
|
||||
@@ -192,6 +192,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
int closefile = (fp == NULL);
|
||||
if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
|
||||
|
||||
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
|
||||
rioInitWithFile(&rdb,fp);
|
||||
rdbstate.rio = &rdb;
|
||||
rdb.update_cksum = rdbLoadProgressCallback;
|
||||
@@ -208,7 +209,6 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
}
|
||||
|
||||
expiretime = -1;
|
||||
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
|
||||
@@ -250,7 +250,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
|
||||
if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
|
||||
goto eoferr;
|
||||
rdbCheckInfo("Selecting DB ID %d", dbid);
|
||||
rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_RESIZEDB) {
|
||||
/* RESIZEDB: Hint about the size of the keys in the currently
|
||||
@@ -308,7 +308,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr,NULL)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
||||
+232
-107
@@ -207,7 +207,8 @@ static struct config {
|
||||
cliSSLconfig sslconfig;
|
||||
long repeat;
|
||||
long interval;
|
||||
int dbnum;
|
||||
int dbnum; /* db num currently selected */
|
||||
int input_dbnum; /* db num user input */
|
||||
int interactive;
|
||||
int shutdown;
|
||||
int monitor_mode;
|
||||
@@ -219,6 +220,7 @@ static struct config {
|
||||
long long lru_test_sample_size;
|
||||
int cluster_mode;
|
||||
int cluster_reissue_command;
|
||||
int cluster_send_asking;
|
||||
int slave_mode;
|
||||
int pipe_mode;
|
||||
int pipe_timeout;
|
||||
@@ -227,7 +229,7 @@ static struct config {
|
||||
int scan_mode;
|
||||
int intrinsic_latency_mode;
|
||||
int intrinsic_latency_duration;
|
||||
char *pattern;
|
||||
sds pattern;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int memkeys;
|
||||
@@ -237,6 +239,7 @@ static struct config {
|
||||
char *auth;
|
||||
int askpass;
|
||||
char *user;
|
||||
int quoted_input; /* Force input args to be treated as quoted strings */
|
||||
int output; /* output mode, see OUTPUT_* defines */
|
||||
int push_output; /* Should we display spontaneous PUSH replies */
|
||||
sds mb_delim;
|
||||
@@ -405,15 +408,17 @@ static void parseRedisUri(const char *uri) {
|
||||
if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) {
|
||||
#ifdef USE_OPENSSL
|
||||
config.tls = 1;
|
||||
curr += strlen(tlsscheme);
|
||||
#else
|
||||
fprintf(stderr,"rediss:// is only supported when redis-cli is compiled with OpenSSL\n");
|
||||
exit(1);
|
||||
#endif
|
||||
} else if (strncasecmp(scheme, curr, strlen(scheme))) {
|
||||
} else if (!strncasecmp(scheme, curr, strlen(scheme))) {
|
||||
curr += strlen(scheme);
|
||||
} else {
|
||||
fprintf(stderr,"Invalid URI scheme\n");
|
||||
exit(1);
|
||||
}
|
||||
curr += strlen(scheme);
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract user info. */
|
||||
@@ -442,7 +447,7 @@ static void parseRedisUri(const char *uri) {
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract database number. */
|
||||
config.dbnum = atoi(curr);
|
||||
config.input_dbnum = atoi(curr);
|
||||
}
|
||||
|
||||
static uint64_t dictSdsHash(const void *key) {
|
||||
@@ -660,7 +665,7 @@ static void cliOutputHelp(int argc, char **argv) {
|
||||
help = entry->org;
|
||||
if (group == -1) {
|
||||
/* Compare all arguments */
|
||||
if (argc == entry->argc) {
|
||||
if (argc <= entry->argc) {
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
|
||||
}
|
||||
@@ -763,6 +768,23 @@ static void freeHintsCallback(void *ptr) {
|
||||
* Networking / parsing
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Unquote a null-terminated string and return it as a binary-safe sds. */
|
||||
static sds unquoteCString(char *str) {
|
||||
int count;
|
||||
sds *unquoted = sdssplitargs(str, &count);
|
||||
sds res = NULL;
|
||||
|
||||
if (unquoted && count == 1) {
|
||||
res = unquoted[0];
|
||||
unquoted[0] = NULL;
|
||||
}
|
||||
|
||||
if (unquoted)
|
||||
sdsfreesplitres(unquoted, count);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Send AUTH command to the server */
|
||||
static int cliAuth(redisContext *ctx, char *user, char *auth) {
|
||||
redisReply *reply;
|
||||
@@ -772,28 +794,42 @@ static int cliAuth(redisContext *ctx, char *user, char *auth) {
|
||||
reply = redisCommand(ctx,"AUTH %s",auth);
|
||||
else
|
||||
reply = redisCommand(ctx,"AUTH %s %s",user,auth);
|
||||
if (reply != NULL) {
|
||||
if (reply->type == REDIS_REPLY_ERROR)
|
||||
fprintf(stderr,"Warning: AUTH failed\n");
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr, "AUTH failed: %s\n", reply->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Send SELECT dbnum to the server */
|
||||
/* Send SELECT input_dbnum to the server */
|
||||
static int cliSelect(void) {
|
||||
redisReply *reply;
|
||||
if (config.dbnum == 0) return REDIS_OK;
|
||||
if (config.input_dbnum == config.dbnum) return REDIS_OK;
|
||||
|
||||
reply = redisCommand(context,"SELECT %d",config.dbnum);
|
||||
if (reply != NULL) {
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
reply = redisCommand(context,"SELECT %d",config.input_dbnum);
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr,"SELECT %d failed: %s\n",config.input_dbnum,reply->str);
|
||||
} else {
|
||||
config.dbnum = config.input_dbnum;
|
||||
cliRefreshPrompt();
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Select RESP3 mode if redis-cli was started with the -3 option. */
|
||||
@@ -802,13 +838,18 @@ static int cliSwitchProto(void) {
|
||||
if (config.resp3 == 0) return REDIS_OK;
|
||||
|
||||
reply = redisCommand(context,"HELLO 3");
|
||||
if (reply != NULL) {
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr,"HELLO 3 failed: %s\n",reply->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Connect to the server. It is possible to pass certain flags to the function:
|
||||
@@ -819,9 +860,14 @@ static int cliConnect(int flags) {
|
||||
if (context == NULL || flags & CC_FORCE) {
|
||||
if (context != NULL) {
|
||||
redisFree(context);
|
||||
config.dbnum = 0;
|
||||
config.in_multi = 0;
|
||||
cliRefreshPrompt();
|
||||
}
|
||||
|
||||
if (config.hostsocket == NULL) {
|
||||
/* Do not use hostsocket when we got redirected in cluster mode */
|
||||
if (config.hostsocket == NULL ||
|
||||
(config.cluster_mode && config.cluster_reissue_command)) {
|
||||
context = redisConnect(config.hostip,config.hostport);
|
||||
} else {
|
||||
context = redisConnectUnix(config.hostsocket);
|
||||
@@ -840,12 +886,15 @@ static int cliConnect(int flags) {
|
||||
if (context->err) {
|
||||
if (!(flags & CC_QUIET)) {
|
||||
fprintf(stderr,"Could not connect to Redis at ");
|
||||
if (config.hostsocket == NULL)
|
||||
fprintf(stderr,"%s:%d: %s\n",
|
||||
if (config.hostsocket == NULL ||
|
||||
(config.cluster_mode && config.cluster_reissue_command))
|
||||
{
|
||||
fprintf(stderr, "%s:%d: %s\n",
|
||||
config.hostip,config.hostport,context->errstr);
|
||||
else
|
||||
} else {
|
||||
fprintf(stderr,"%s: %s\n",
|
||||
config.hostsocket,context->errstr);
|
||||
}
|
||||
}
|
||||
redisFree(context);
|
||||
context = NULL;
|
||||
@@ -876,6 +925,29 @@ static int cliConnect(int flags) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* In cluster, if server replies ASK, we will redirect to a different node.
|
||||
* Before sending the real command, we need to send ASKING command first. */
|
||||
static int cliSendAsking() {
|
||||
redisReply *reply;
|
||||
|
||||
config.cluster_send_asking = 0;
|
||||
if (context == NULL) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
reply = redisCommand(context,"ASKING");
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
result = REDIS_ERR;
|
||||
fprintf(stderr,"ASKING failed: %s\n",reply->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cliPrintContextError(void) {
|
||||
if (context == NULL) return;
|
||||
fprintf(stderr,"Error: %s\n",context->errstr);
|
||||
@@ -1104,6 +1176,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
out = sdscatprintf(out,"%s",r->str);
|
||||
break;
|
||||
case REDIS_REPLY_SET:
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_PUSH:
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
@@ -1162,6 +1235,7 @@ static sds cliFormatReplyCSV(redisReply *r) {
|
||||
out = sdscat(out,r->integer ? "true" : "false");
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_SET:
|
||||
case REDIS_REPLY_PUSH:
|
||||
case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
@@ -1249,7 +1323,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
/* Check if we need to connect to a different node and reissue the
|
||||
* request. */
|
||||
if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
|
||||
(!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK")))
|
||||
(!strncmp(reply->str,"MOVED ",6) || !strncmp(reply->str,"ASK ",4)))
|
||||
{
|
||||
char *p = reply->str, *s;
|
||||
int slot;
|
||||
@@ -1273,6 +1347,9 @@ static int cliReadReply(int output_raw_strings) {
|
||||
printf("-> Redirected to slot [%d] located at %s:%d\n",
|
||||
slot, config.hostip, config.hostport);
|
||||
config.cluster_reissue_command = 1;
|
||||
if (!strncmp(reply->str,"ASK ",4)) {
|
||||
config.cluster_send_asking = 1;
|
||||
}
|
||||
cliRefreshPrompt();
|
||||
} else if (!config.interactive && config.set_errcode &&
|
||||
reply->type == REDIS_REPLY_ERROR)
|
||||
@@ -1285,6 +1362,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
if (output) {
|
||||
out = cliFormatReply(reply, config.output, output_raw_strings);
|
||||
fwrite(out,sdslen(out),1,stdout);
|
||||
fflush(stdout);
|
||||
sdsfree(out);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
@@ -1406,7 +1484,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
if (!strcasecmp(command,"select") && argc == 2 &&
|
||||
config.last_cmd_type != REDIS_REPLY_ERROR)
|
||||
{
|
||||
config.dbnum = atoi(argv[1]);
|
||||
config.input_dbnum = config.dbnum = atoi(argv[1]);
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3)) {
|
||||
cliSelect();
|
||||
@@ -1418,21 +1496,23 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"exec") && argc == 1 && config.in_multi) {
|
||||
config.in_multi = 0;
|
||||
if (config.last_cmd_type == REDIS_REPLY_ERROR) {
|
||||
config.dbnum = config.pre_multi_dbnum;
|
||||
if (config.last_cmd_type == REDIS_REPLY_ERROR ||
|
||||
config.last_cmd_type == REDIS_REPLY_NIL)
|
||||
{
|
||||
config.input_dbnum = config.dbnum = config.pre_multi_dbnum;
|
||||
}
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"discard") && argc == 1 &&
|
||||
config.last_cmd_type != REDIS_REPLY_ERROR)
|
||||
{
|
||||
config.in_multi = 0;
|
||||
config.dbnum = config.pre_multi_dbnum;
|
||||
config.input_dbnum = config.dbnum = config.pre_multi_dbnum;
|
||||
cliRefreshPrompt();
|
||||
}
|
||||
}
|
||||
if (config.cluster_reissue_command){
|
||||
/* If we need to reissue the command, break to prevent a
|
||||
further 'repeat' number of dud interations */
|
||||
further 'repeat' number of dud interactions */
|
||||
break;
|
||||
}
|
||||
if (config.interval) usleep(config.interval);
|
||||
@@ -1513,7 +1593,7 @@ static int parseOptions(int argc, char **argv) {
|
||||
double seconds = atof(argv[++i]);
|
||||
config.interval = seconds*1000000;
|
||||
} else if (!strcmp(argv[i],"-n") && !lastarg) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
config.input_dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||
config.no_auth_warning = 1;
|
||||
} else if (!strcmp(argv[i], "--askpass")) {
|
||||
@@ -1530,6 +1610,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.output = OUTPUT_RAW;
|
||||
} else if (!strcmp(argv[i],"--no-raw")) {
|
||||
config.output = OUTPUT_STANDARD;
|
||||
} else if (!strcmp(argv[i],"--quoted-input")) {
|
||||
config.quoted_input = 1;
|
||||
} else if (!strcmp(argv[i],"--csv")) {
|
||||
config.output = OUTPUT_CSV;
|
||||
} else if (!strcmp(argv[i],"--latency")) {
|
||||
@@ -1554,7 +1636,15 @@ static int parseOptions(int argc, char **argv) {
|
||||
} else if (!strcmp(argv[i],"--scan")) {
|
||||
config.scan_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--pattern") && !lastarg) {
|
||||
config.pattern = argv[++i];
|
||||
sdsfree(config.pattern);
|
||||
config.pattern = sdsnew(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--quoted-pattern") && !lastarg) {
|
||||
sdsfree(config.pattern);
|
||||
config.pattern = unquoteCString(argv[++i]);
|
||||
if (!config.pattern) {
|
||||
fprintf(stderr,"Invalid quoted string specified for --quoted-pattern.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
|
||||
config.intrinsic_latency_mode = 1;
|
||||
config.intrinsic_latency_duration = atoi(argv[++i]);
|
||||
@@ -1741,6 +1831,11 @@ static int parseOptions(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hostsocket && config.cluster_mode) {
|
||||
fprintf(stderr,"Options -c and -s are mutually exclusive.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* --ldb requires --eval. */
|
||||
if (config.eval_ldb && config.eval == NULL) {
|
||||
fprintf(stderr,"Options --ldb and --ldb-sync-mode require --eval.\n");
|
||||
@@ -1838,6 +1933,7 @@ static void usage(void) {
|
||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||
" not a tty).\n"
|
||||
" --no-raw Force formatted output even when STDOUT is not a tty.\n"
|
||||
" --quoted-input Force input to be handled as quoted strings.\n"
|
||||
" --csv Output in CSV format.\n"
|
||||
" --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n"
|
||||
" STDOUT is a tty but can be overriden with --show-pushes no.\n"
|
||||
@@ -1858,6 +1954,7 @@ static void usage(void) {
|
||||
" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\n"
|
||||
" --replica Simulate a replica showing commands received from the master.\n"
|
||||
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
|
||||
" Use filename of \"-\" to write to stdout.\n"
|
||||
" --pipe Transfer raw Redis protocol from stdin to server.\n"
|
||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
||||
" no reply is received within <n> seconds.\n"
|
||||
@@ -1873,6 +1970,8 @@ static void usage(void) {
|
||||
" --scan List all keys using the SCAN command.\n"
|
||||
" --pattern <pat> Keys pattern when using the --scan, --bigkeys or --hotkeys\n"
|
||||
" options (default: *).\n"
|
||||
" --quoted-pattern <pat> Same as --pattern, but the specified string can be\n"
|
||||
" quoted, in order to pass an otherwise non binary-safe string.\n"
|
||||
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
|
||||
" The test will run for the specified amount of seconds.\n"
|
||||
" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
|
||||
@@ -1898,6 +1997,7 @@ static void usage(void) {
|
||||
" redis-cli get mypasswd\n"
|
||||
" redis-cli -r 100 lpush mylist x\n"
|
||||
" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
|
||||
" redis-cli --quoted-input set '\"null-\\x00-separated\"' value\n"
|
||||
" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
|
||||
" redis-cli --scan --pattern '*:12345*'\n"
|
||||
"\n"
|
||||
@@ -1927,43 +2027,58 @@ static int confirmWithYes(char *msg, int ignore_force) {
|
||||
return (nread != 0 && !strcmp("yes", buf));
|
||||
}
|
||||
|
||||
/* Turn the plain C strings into Sds strings */
|
||||
static char **convertToSds(int count, char** args) {
|
||||
int j;
|
||||
char **sds = zmalloc(sizeof(char*)*count);
|
||||
/* Create an sds array from argv, either as-is or by dequoting every
|
||||
* element. When quoted is non-zero, may return a NULL to indicate an
|
||||
* invalid quoted string.
|
||||
*/
|
||||
static sds *getSdsArrayFromArgv(int argc, char **argv, int quoted) {
|
||||
sds *res = sds_malloc(sizeof(sds) * argc);
|
||||
|
||||
for(j = 0; j < count; j++)
|
||||
sds[j] = sdsnew(args[j]);
|
||||
for (int j = 0; j < argc; j++) {
|
||||
if (quoted) {
|
||||
sds unquoted = unquoteCString(argv[j]);
|
||||
if (!unquoted) {
|
||||
while (--j >= 0) sdsfree(res[j]);
|
||||
sds_free(res);
|
||||
return NULL;
|
||||
}
|
||||
res[j] = unquoted;
|
||||
} else {
|
||||
res[j] = sdsnew(argv[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return sds;
|
||||
}
|
||||
|
||||
static void freeConvertedSds(int count, char **sds) {
|
||||
int j;
|
||||
for (j = 0; j < count; j++)
|
||||
sdsfree(sds[j]);
|
||||
zfree(sds);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int issueCommandRepeat(int argc, char **argv, long repeat) {
|
||||
while (1) {
|
||||
if (config.cluster_reissue_command || context == NULL ||
|
||||
context->err == REDIS_ERR_IO || context->err == REDIS_ERR_EOF)
|
||||
{
|
||||
if (cliConnect(CC_FORCE) != REDIS_OK) {
|
||||
cliPrintContextError();
|
||||
config.cluster_reissue_command = 0;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
config.cluster_reissue_command = 0;
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
cliConnect(CC_FORCE);
|
||||
|
||||
/* If we still cannot send the command print error.
|
||||
* We'll try to reconnect the next time. */
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
if (config.cluster_send_asking) {
|
||||
if (cliSendAsking() != REDIS_OK) {
|
||||
cliPrintContextError();
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
cliPrintContextError();
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Issue the command again if we got redirected in cluster mode */
|
||||
if (config.cluster_mode && config.cluster_reissue_command) {
|
||||
cliConnect(CC_FORCE);
|
||||
} else {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
@@ -2175,17 +2290,19 @@ static void repl(void) {
|
||||
|
||||
static int noninteractive(int argc, char **argv) {
|
||||
int retval = 0;
|
||||
|
||||
argv = convertToSds(argc, argv);
|
||||
if (config.stdinarg) {
|
||||
argv = zrealloc(argv, (argc+1)*sizeof(char*));
|
||||
argv[argc] = readArgFromStdin();
|
||||
retval = issueCommand(argc+1, argv);
|
||||
sdsfree(argv[argc]);
|
||||
} else {
|
||||
retval = issueCommand(argc, argv);
|
||||
sds *sds_args = getSdsArrayFromArgv(argc, argv, config.quoted_input);
|
||||
if (!sds_args) {
|
||||
printf("Invalid quoted string\n");
|
||||
return 1;
|
||||
}
|
||||
freeConvertedSds(argc, argv);
|
||||
if (config.stdinarg) {
|
||||
sds_args = sds_realloc(sds_args, (argc + 1) * sizeof(sds));
|
||||
sds_args[argc] = readArgFromStdin();
|
||||
argc++;
|
||||
}
|
||||
|
||||
retval = issueCommand(argc, sds_args);
|
||||
sdsfreesplitres(sds_args, argc);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -2910,8 +3027,12 @@ static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
|
||||
else types = sdsempty();
|
||||
/* Master type 'm' is always set as the first character of the
|
||||
* types string. */
|
||||
if (!node->replicate) types = sdscatprintf(types, "m%s", types);
|
||||
else types = sdscat(types, "s");
|
||||
if (node->replicate) types = sdscat(types, "s");
|
||||
else {
|
||||
sds s = sdscatsds(sdsnew("m"), types);
|
||||
sdsfree(types);
|
||||
types = s;
|
||||
}
|
||||
dictReplace(related, key, types);
|
||||
}
|
||||
/* Now it's trivial to check, for each related group having the
|
||||
@@ -2969,7 +3090,7 @@ static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
|
||||
ip_count,
|
||||
&offenders,
|
||||
&offending_len);
|
||||
if (score == 0) break; // Optimal anti affinity reached
|
||||
if (score == 0 || offending_len == 0) break; // Optimal anti affinity reached
|
||||
/* We'll try to randomly swap a slave's assigned master causing
|
||||
* an affinity problem with another random slave, to see if we
|
||||
* can improve the affinity. */
|
||||
@@ -5432,7 +5553,7 @@ static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array) {
|
||||
static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
|
||||
clusterManagerNode **nodeptr)
|
||||
{
|
||||
assert(array->nodes < (array->nodes + array->len));
|
||||
assert(array->len > 0);
|
||||
/* If the first node to be shifted is not NULL, decrement count. */
|
||||
if (*array->nodes != NULL) array->count--;
|
||||
/* Store the first node to be shifted into 'nodeptr'. */
|
||||
@@ -5445,7 +5566,7 @@ static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
|
||||
static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
|
||||
clusterManagerNode *node)
|
||||
{
|
||||
assert(array->nodes < (array->nodes + array->len));
|
||||
assert(array->len > 0);
|
||||
assert(node != NULL);
|
||||
assert(array->count < array->len);
|
||||
array->nodes[array->count++] = node;
|
||||
@@ -6485,9 +6606,9 @@ static int clusterManagerCommandImport(int argc, char **argv) {
|
||||
}
|
||||
|
||||
if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COPY)
|
||||
strcat(cmdfmt, " %s");
|
||||
cmdfmt = sdscat(cmdfmt," COPY");
|
||||
if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_REPLACE)
|
||||
strcat(cmdfmt, " %s");
|
||||
cmdfmt = sdscat(cmdfmt," REPLACE");
|
||||
|
||||
/* Use SCAN to iterate over the keys, migrating to the
|
||||
* right node as needed. */
|
||||
@@ -6519,8 +6640,7 @@ static int clusterManagerCommandImport(int argc, char **argv) {
|
||||
printf("Migrating %s to %s:%d: ", key, target->ip, target->port);
|
||||
redisReply *r = reconnectingRedisCommand(src_ctx, cmdfmt,
|
||||
target->ip, target->port,
|
||||
key, 0, timeout,
|
||||
"COPY", "REPLACE");
|
||||
key, 0, timeout);
|
||||
if (!r || r->type == REDIS_REPLY_ERROR) {
|
||||
if (r && r->str) {
|
||||
clusterManagerLogErr("Source %s:%d replied with "
|
||||
@@ -6822,7 +6942,7 @@ void showLatencyDistSamples(struct distsamples *samples, long long tot) {
|
||||
printf("\033[38;5;0m"); /* Set foreground color to black. */
|
||||
for (j = 0; ; j++) {
|
||||
int coloridx =
|
||||
ceil((float) samples[j].count / tot * (spectrum_palette_size-1));
|
||||
ceil((double) samples[j].count / tot * (spectrum_palette_size-1));
|
||||
int color = spectrum_palette[coloridx];
|
||||
printf("\033[48;5;%dm%c", (int)color, samples[j].character);
|
||||
samples[j].count = 0;
|
||||
@@ -6936,7 +7056,7 @@ static void latencyDistMode(void) {
|
||||
#define RDB_EOF_MARK_SIZE 40
|
||||
|
||||
void sendReplconf(const char* arg1, const char* arg2) {
|
||||
printf("sending REPLCONF %s %s\n", arg1, arg2);
|
||||
fprintf(stderr, "sending REPLCONF %s %s\n", arg1, arg2);
|
||||
redisReply *reply = redisCommand(context, "REPLCONF %s %s", arg1, arg2);
|
||||
|
||||
/* Handle any error conditions */
|
||||
@@ -6996,7 +7116,7 @@ unsigned long long sendSync(redisContext *c, char *out_eof) {
|
||||
}
|
||||
*p = '\0';
|
||||
if (buf[0] == '-') {
|
||||
printf("SYNC with master failed: %s\n", buf);
|
||||
fprintf(stderr, "SYNC with master failed: %s\n", buf);
|
||||
exit(1);
|
||||
}
|
||||
if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= RDB_EOF_MARK_SIZE) {
|
||||
@@ -7101,8 +7221,9 @@ static void getRDB(clusterManagerNode *node) {
|
||||
payload, filename);
|
||||
}
|
||||
|
||||
int write_to_stdout = !strcmp(filename,"-");
|
||||
/* Write to file. */
|
||||
if (!strcmp(filename,"-")) {
|
||||
if (write_to_stdout) {
|
||||
fd = STDOUT_FILENO;
|
||||
} else {
|
||||
fd = open(filename, O_CREAT|O_WRONLY, 0644);
|
||||
@@ -7144,7 +7265,7 @@ static void getRDB(clusterManagerNode *node) {
|
||||
}
|
||||
if (usemark) {
|
||||
payload = ULLONG_MAX - payload - RDB_EOF_MARK_SIZE;
|
||||
if (ftruncate(fd, payload) == -1)
|
||||
if (!write_to_stdout && ftruncate(fd, payload) == -1)
|
||||
fprintf(stderr,"ftruncate failed: %s.\n", strerror(errno));
|
||||
fprintf(stderr,"Transfer finished with success after %llu bytes\n", payload);
|
||||
} else {
|
||||
@@ -7153,7 +7274,10 @@ static void getRDB(clusterManagerNode *node) {
|
||||
redisFree(s); /* Close the connection ASAP as fsync() may take time. */
|
||||
if (node)
|
||||
node->context = NULL;
|
||||
fsync(fd);
|
||||
if (!write_to_stdout && fsync(fd) == -1) {
|
||||
fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
close(fd);
|
||||
if (node) {
|
||||
sdsfree(filename);
|
||||
@@ -7329,8 +7453,8 @@ static redisReply *sendScan(unsigned long long *it) {
|
||||
redisReply *reply;
|
||||
|
||||
if (config.pattern)
|
||||
reply = redisCommand(context,"SCAN %llu MATCH %s",
|
||||
*it,config.pattern);
|
||||
reply = redisCommand(context, "SCAN %llu MATCH %b",
|
||||
*it, config.pattern, sdslen(config.pattern));
|
||||
else
|
||||
reply = redisCommand(context,"SCAN %llu",*it);
|
||||
|
||||
@@ -7365,8 +7489,14 @@ static int getDbSize(void) {
|
||||
|
||||
reply = redisCommand(context, "DBSIZE");
|
||||
|
||||
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
|
||||
fprintf(stderr, "Couldn't determine DBSIZE!\n");
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
exit(1);
|
||||
} else if (reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "Couldn't determine DBSIZE: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else if (reply->type != REDIS_REPLY_INTEGER) {
|
||||
fprintf(stderr, "Non INTEGER response from DBSIZE!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -7942,23 +8072,16 @@ static void scanMode(void) {
|
||||
unsigned long long cur = 0;
|
||||
|
||||
do {
|
||||
if (config.pattern)
|
||||
reply = redisCommand(context,"SCAN %llu MATCH %s",
|
||||
cur,config.pattern);
|
||||
else
|
||||
reply = redisCommand(context,"SCAN %llu",cur);
|
||||
if (reply == NULL) {
|
||||
printf("I/O error\n");
|
||||
exit(1);
|
||||
} else if (reply->type == REDIS_REPLY_ERROR) {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else {
|
||||
unsigned int j;
|
||||
|
||||
cur = strtoull(reply->element[0]->str,NULL,10);
|
||||
for (j = 0; j < reply->element[1]->elements; j++)
|
||||
reply = sendScan(&cur);
|
||||
for (unsigned int j = 0; j < reply->element[1]->elements; j++) {
|
||||
if (config.output == OUTPUT_STANDARD) {
|
||||
sds out = sdscatrepr(sdsempty(), reply->element[1]->element[j]->str,
|
||||
reply->element[1]->element[j]->len);
|
||||
printf("%s\n", out);
|
||||
sdsfree(out);
|
||||
} else {
|
||||
printf("%s\n", reply->element[1]->element[j]->str);
|
||||
}
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
} while(cur != 0);
|
||||
@@ -8152,6 +8275,7 @@ int main(int argc, char **argv) {
|
||||
config.repeat = 1;
|
||||
config.interval = 0;
|
||||
config.dbnum = 0;
|
||||
config.input_dbnum = 0;
|
||||
config.interactive = 0;
|
||||
config.shutdown = 0;
|
||||
config.monitor_mode = 0;
|
||||
@@ -8162,6 +8286,7 @@ int main(int argc, char **argv) {
|
||||
config.lru_test_mode = 0;
|
||||
config.lru_test_sample_size = 0;
|
||||
config.cluster_mode = 0;
|
||||
config.cluster_send_asking = 0;
|
||||
config.slave_mode = 0;
|
||||
config.getrdb_mode = 0;
|
||||
config.stat_mode = 0;
|
||||
|
||||
+18
-3
@@ -160,13 +160,14 @@ This flag should not be used directly by the module.
|
||||
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
|
||||
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */
|
||||
#define REDISMODULE_NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
|
||||
#define REDISMODULE_NOTIFY_MODULE (1<<13) /* d, module key space notification */
|
||||
|
||||
/* Next notification flag, must be updated when adding new flags above!
|
||||
This flag should not be used directly by the module.
|
||||
* Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */
|
||||
#define _REDISMODULE_NOTIFY_NEXT (1<<13)
|
||||
#define _REDISMODULE_NOTIFY_NEXT (1<<14)
|
||||
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE) /* A */
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
* field deletion, and that is impossible to be a valid pointer. */
|
||||
@@ -193,6 +194,12 @@ This flag should not be used directly by the module.
|
||||
|
||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||
|
||||
/* Logging level strings */
|
||||
#define REDISMODULE_LOGLEVEL_DEBUG "debug"
|
||||
#define REDISMODULE_LOGLEVEL_VERBOSE "verbose"
|
||||
#define REDISMODULE_LOGLEVEL_NOTICE "notice"
|
||||
#define REDISMODULE_LOGLEVEL_WARNING "warning"
|
||||
|
||||
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
|
||||
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||
@@ -635,6 +642,8 @@ REDISMODULE_API char * (*RedisModule_StringDMA)(RedisModuleKey *key, size_t *len
|
||||
REDISMODULE_API int (*RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen) REDISMODULE_ATTR;
|
||||
REDISMODULE_API mstime_t (*RedisModule_GetExpire)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
|
||||
REDISMODULE_API mstime_t (*RedisModule_GetAbsExpire)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SetAbsExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ResetDataset)(int restart_aof, int async) REDISMODULE_ATTR;
|
||||
REDISMODULE_API unsigned long long (*RedisModule_DbSize)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_RandomKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
@@ -665,6 +674,7 @@ REDISMODULE_API long long (*RedisModule_StreamTrimByID)(RedisModuleKey *key, int
|
||||
REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR;
|
||||
REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientUserNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
@@ -826,6 +836,7 @@ REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ct
|
||||
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
@@ -834,7 +845,7 @@ REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, un
|
||||
REDISMODULE_API int (*RedisModule_DefragCursorGet)(RedisModuleDefragCtx *ctx, unsigned long *cursor) REDISMODULE_ATTR;
|
||||
#endif
|
||||
|
||||
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
|
||||
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR_UNUSED;
|
||||
@@ -906,6 +917,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(StringTruncate);
|
||||
REDISMODULE_GET_API(GetExpire);
|
||||
REDISMODULE_GET_API(SetExpire);
|
||||
REDISMODULE_GET_API(GetAbsExpire);
|
||||
REDISMODULE_GET_API(SetAbsExpire);
|
||||
REDISMODULE_GET_API(ResetDataset);
|
||||
REDISMODULE_GET_API(DbSize);
|
||||
REDISMODULE_GET_API(RandomKey);
|
||||
@@ -936,6 +949,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(IsKeysPositionRequest);
|
||||
REDISMODULE_GET_API(KeyAtPos);
|
||||
REDISMODULE_GET_API(GetClientId);
|
||||
REDISMODULE_GET_API(GetClientUserNameById);
|
||||
REDISMODULE_GET_API(GetContextFlags);
|
||||
REDISMODULE_GET_API(AvoidReplicaTraffic);
|
||||
REDISMODULE_GET_API(PoolAlloc);
|
||||
@@ -1095,6 +1109,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(AuthenticateClientWithUser);
|
||||
REDISMODULE_GET_API(GetClientCertificate);
|
||||
REDISMODULE_GET_API(GetCommandKeys);
|
||||
REDISMODULE_GET_API(GetCurrentCommandName);
|
||||
REDISMODULE_GET_API(RegisterDefragFunc);
|
||||
REDISMODULE_GET_API(DefragAlloc);
|
||||
REDISMODULE_GET_API(DefragRedisModuleString);
|
||||
|
||||
+78
-35
@@ -377,6 +377,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
|
||||
}
|
||||
|
||||
void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv, int argc) {
|
||||
if (!(listLength(server.monitors) && !server.loading)) return;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int j;
|
||||
@@ -520,18 +521,12 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
|
||||
*
|
||||
* On success return C_OK, otherwise C_ERR is returned and we proceed
|
||||
* with the usual full resync. */
|
||||
int masterTryPartialResynchronization(client *c) {
|
||||
long long psync_offset, psync_len;
|
||||
int masterTryPartialResynchronization(client *c, long long psync_offset) {
|
||||
long long psync_len;
|
||||
char *master_replid = c->argv[1]->ptr;
|
||||
char buf[128];
|
||||
int buflen;
|
||||
|
||||
/* Parse the replication offset asked by the slave. Go to full sync
|
||||
* on parse error: this should never happen but we try to handle
|
||||
* it in a robust way compared to aborting. */
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&psync_offset,NULL) !=
|
||||
C_OK) goto need_full_resync;
|
||||
|
||||
/* Is the replication ID of this master the same advertised by the wannabe
|
||||
* slave via PSYNC? If the replication ID changed this master has a
|
||||
* different replication history, and there is no way to continue.
|
||||
@@ -778,7 +773,14 @@ void syncCommand(client *c) {
|
||||
* So the slave knows the new replid and offset to try a PSYNC later
|
||||
* if the connection with the master is lost. */
|
||||
if (!strcasecmp(c->argv[0]->ptr,"psync")) {
|
||||
if (masterTryPartialResynchronization(c) == C_OK) {
|
||||
long long psync_offset;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[2], &psync_offset, NULL) != C_OK) {
|
||||
serverLog(LL_WARNING, "Replica %s asks for synchronization but with a wrong offset",
|
||||
replicationGetSlaveName(c));
|
||||
return;
|
||||
}
|
||||
|
||||
if (masterTryPartialResynchronization(c, psync_offset) == C_OK) {
|
||||
server.stat_sync_partial_ok++;
|
||||
return; /* No full resync needed, return. */
|
||||
} else {
|
||||
@@ -892,17 +894,34 @@ void syncCommand(client *c) {
|
||||
}
|
||||
|
||||
/* REPLCONF <option> <value> <option> <value> ...
|
||||
* This command is used by a slave in order to configure the replication
|
||||
* This command is used by a replica in order to configure the replication
|
||||
* process before starting it with the SYNC command.
|
||||
* This command is also used by a master in order to get the replication
|
||||
* offset from a replica.
|
||||
*
|
||||
* Currently the only use of this command is to communicate to the master
|
||||
* what is the listening port of the Slave redis instance, so that the
|
||||
* master can accurately list slaves and their listening ports in
|
||||
* the INFO output.
|
||||
* Currently we support these options:
|
||||
*
|
||||
* In the future the same command can be used in order to configure
|
||||
* the replication to initiate an incremental replication instead of a
|
||||
* full resync. */
|
||||
* - listening-port <port>
|
||||
* - ip-address <ip>
|
||||
* What is the listening ip and port of the Replica redis instance, so that
|
||||
* the master can accurately lists replicas and their listening ports in the
|
||||
* INFO output.
|
||||
*
|
||||
* - capa <eof|psync2>
|
||||
* What is the capabilities of this instance.
|
||||
* eof: supports EOF-style RDB transfer for diskless replication.
|
||||
* psync2: supports PSYNC v2, so understands +CONTINUE <new repl ID>.
|
||||
*
|
||||
* - ack <offset>
|
||||
* Replica informs the master the amount of replication stream that it
|
||||
* processed so far.
|
||||
*
|
||||
* - getack
|
||||
* Unlike other subcommands, this is used by master to get the replication
|
||||
* offset from a replica.
|
||||
*
|
||||
* - rdb-only
|
||||
* Only wants RDB snapshot without replication buffer. */
|
||||
void replconfCommand(client *c) {
|
||||
int j;
|
||||
|
||||
@@ -1136,6 +1155,8 @@ void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
|
||||
if (!connHasWriteHandler(conn))
|
||||
return;
|
||||
connSetWriteHandler(conn, NULL);
|
||||
client *slave = connGetPrivateData(conn);
|
||||
slave->repl_last_partial_write = 0;
|
||||
server.rdb_pipe_numconns_writing--;
|
||||
/* if there are no more writes for now for this conn, or write error: */
|
||||
if (server.rdb_pipe_numconns_writing == 0) {
|
||||
@@ -1163,8 +1184,10 @@ void rdbPipeWriteHandler(struct connection *conn) {
|
||||
} else {
|
||||
slave->repldboff += nwritten;
|
||||
atomicIncr(server.stat_net_output_bytes, nwritten);
|
||||
if (slave->repldboff < server.rdb_pipe_bufflen)
|
||||
if (slave->repldboff < server.rdb_pipe_bufflen) {
|
||||
slave->repl_last_partial_write = server.unixtime;
|
||||
return; /* more data to write.. */
|
||||
}
|
||||
}
|
||||
rdbPipeWriteHandlerConnRemoved(conn);
|
||||
}
|
||||
@@ -1245,6 +1268,7 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
|
||||
/* If we were unable to write all the data to one of the replicas,
|
||||
* setup write handler (and disable pipe read handler, below) */
|
||||
if (nwritten != server.rdb_pipe_bufflen) {
|
||||
slave->repl_last_partial_write = server.unixtime;
|
||||
server.rdb_pipe_numconns_writing++;
|
||||
connSetWriteHandler(conn, rdbPipeWriteHandler);
|
||||
}
|
||||
@@ -1281,6 +1305,9 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
/* Note: there's a chance we got here from within the REPLCONF ACK command
|
||||
* so we must avoid using freeClient, otherwise we'll crash on our way up. */
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = ln->value;
|
||||
@@ -1289,7 +1316,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
struct redis_stat buf;
|
||||
|
||||
if (bgsaveerr != C_OK) {
|
||||
freeClient(slave);
|
||||
freeClientAsync(slave);
|
||||
serverLog(LL_WARNING,"SYNC failed. BGSAVE child returned an error");
|
||||
continue;
|
||||
}
|
||||
@@ -1334,7 +1361,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
} else {
|
||||
if ((slave->repldbfd = open(server.rdb_filename,O_RDONLY)) == -1 ||
|
||||
redis_fstat(slave->repldbfd,&buf) == -1) {
|
||||
freeClient(slave);
|
||||
freeClientAsync(slave);
|
||||
serverLog(LL_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
@@ -1346,7 +1373,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
|
||||
connSetWriteHandler(slave->conn,NULL);
|
||||
if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) {
|
||||
freeClient(slave);
|
||||
freeClientAsync(slave);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1873,8 +1900,7 @@ void readSyncBulkPayload(connection *conn) {
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
|
||||
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
|
||||
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections.\n");
|
||||
redisCommunicateSystemd("READY=1\n");
|
||||
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections in read-write mode.\n");
|
||||
}
|
||||
|
||||
/* Send the initial ACK immediately to put this replica in online state. */
|
||||
@@ -2434,8 +2460,7 @@ void syncWithMaster(connection *conn) {
|
||||
if (psync_result == PSYNC_CONTINUE) {
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
|
||||
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections.\n");
|
||||
redisCommunicateSystemd("READY=1\n");
|
||||
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections in read-write mode.\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2683,6 +2708,9 @@ void replicationUnsetMaster(void) {
|
||||
* starting from now. Otherwise the backlog will be freed after a
|
||||
* failover if slaves do not connect immediately. */
|
||||
server.repl_no_slaves_since = server.unixtime;
|
||||
|
||||
/* Reset down time so it'll be ready for when we turn into replica again. */
|
||||
server.repl_down_since = 0;
|
||||
|
||||
/* Fire the role change modules event. */
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
|
||||
@@ -2758,7 +2786,7 @@ void replicaofCommand(client *c) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
/* Check if we are already attached to the specified slave */
|
||||
/* Check if we are already attached to the specified master */
|
||||
if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr)
|
||||
&& server.masterport == port) {
|
||||
serverLog(LL_NOTICE,"REPLICAOF would result into synchronization "
|
||||
@@ -3375,13 +3403,28 @@ void replicationCron(void) {
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = ln->value;
|
||||
|
||||
if (slave->replstate != SLAVE_STATE_ONLINE) continue;
|
||||
if (slave->flags & CLIENT_PRE_PSYNC) continue;
|
||||
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
|
||||
{
|
||||
serverLog(LL_WARNING, "Disconnecting timedout replica: %s",
|
||||
replicationGetSlaveName(slave));
|
||||
freeClient(slave);
|
||||
if (slave->replstate == SLAVE_STATE_ONLINE) {
|
||||
if (slave->flags & CLIENT_PRE_PSYNC)
|
||||
continue;
|
||||
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout) {
|
||||
serverLog(LL_WARNING, "Disconnecting timedout replica (streaming sync): %s",
|
||||
replicationGetSlaveName(slave));
|
||||
freeClient(slave);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* We consider disconnecting only diskless replicas because disk-based replicas aren't fed
|
||||
* by the fork child so if a disk-based replica is stuck it doesn't prevent the fork child
|
||||
* from terminating. */
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END && server.rdb_child_type == RDB_CHILD_TYPE_SOCKET) {
|
||||
if (slave->repl_last_partial_write != 0 &&
|
||||
(server.unixtime - slave->repl_last_partial_write) > server.repl_timeout)
|
||||
{
|
||||
serverLog(LL_WARNING, "Disconnecting timedout replica (full sync): %s",
|
||||
replicationGetSlaveName(slave));
|
||||
freeClient(slave);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3546,7 +3589,7 @@ void abortFailover(const char *err) {
|
||||
}
|
||||
|
||||
/*
|
||||
* FAILOVER [TO <HOST> <IP> [FORCE]] [ABORT] [TIMEOUT <timeout>]
|
||||
* FAILOVER [TO <HOST> <PORT> [FORCE]] [ABORT] [TIMEOUT <timeout>]
|
||||
*
|
||||
* This command will coordinate a failover between the master and one
|
||||
* of its replicas. The happy path contains the following steps:
|
||||
@@ -3649,7 +3692,7 @@ void failoverCommand(client *c) {
|
||||
client *replica = findReplica(host, port);
|
||||
|
||||
if (replica == NULL) {
|
||||
addReplyError(c,"FAILOVER target HOST and IP is not "
|
||||
addReplyError(c,"FAILOVER target HOST and PORT is not "
|
||||
"a replica.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
r->io.file.buffered >= r->io.file.autosync)
|
||||
{
|
||||
fflush(r->io.file.fp);
|
||||
redis_fsync(fileno(r->io.file.fp));
|
||||
if (redis_fsync(fileno(r->io.file.fp)) == -1) return 0;
|
||||
r->io.file.buffered = 0;
|
||||
}
|
||||
return retval;
|
||||
@@ -160,7 +160,7 @@ void rioInitWithFile(rio *r, FILE *fp) {
|
||||
}
|
||||
|
||||
/* ------------------- Connection implementation -------------------
|
||||
* We use this RIO implemetnation when reading an RDB file directly from
|
||||
* We use this RIO implementation when reading an RDB file directly from
|
||||
* the connection to the memory via rdbLoadRio(), thus this implementation
|
||||
* only implements reading from a connection that is, normally,
|
||||
* just a socket. */
|
||||
@@ -211,7 +211,9 @@ static size_t rioConnRead(rio *r, void *buf, size_t len) {
|
||||
int retval = connRead(r->io.conn.conn,
|
||||
(char*)r->io.conn.buf + sdslen(r->io.conn.buf),
|
||||
toread);
|
||||
if (retval <= 0) {
|
||||
if (retval == 0) {
|
||||
return 0;
|
||||
} else if (retval < 0) {
|
||||
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||
return 0;
|
||||
}
|
||||
@@ -262,7 +264,7 @@ void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
|
||||
sdsclear(r->io.conn.buf);
|
||||
}
|
||||
|
||||
/* Release the RIO tream. Optionally returns the unread buffered data
|
||||
/* Release the RIO stream. Optionally returns the unread buffered data
|
||||
* when the SDS pointer 'remaining' is passed. */
|
||||
void rioFreeConn(rio *r, sds *remaining) {
|
||||
if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {
|
||||
|
||||
+330
-117
@@ -31,6 +31,7 @@
|
||||
#include "sha1.h"
|
||||
#include "rand.h"
|
||||
#include "cluster.h"
|
||||
#include "monotonic.h"
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
@@ -38,6 +39,97 @@
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Globals that are added by the Lua libraries */
|
||||
static char *libraries_allow_list[] = {
|
||||
"string",
|
||||
"cjson",
|
||||
"bit",
|
||||
"cmsgpack",
|
||||
"math",
|
||||
"table",
|
||||
"struct",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Redis Lua API globals */
|
||||
static char *redis_api_allow_list[] = {
|
||||
"redis",
|
||||
"__redis__err__handler", /* error handler for eval, currently located on globals.
|
||||
Should move to registry. */
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Lua builtins */
|
||||
static char *lua_builtins_allow_list[] = {
|
||||
"xpcall",
|
||||
"tostring",
|
||||
"getfenv",
|
||||
"setmetatable",
|
||||
"next",
|
||||
"assert",
|
||||
"tonumber",
|
||||
"rawequal",
|
||||
"collectgarbage",
|
||||
"getmetatable",
|
||||
"rawset",
|
||||
"pcall",
|
||||
"coroutine",
|
||||
"type",
|
||||
"_G",
|
||||
"select",
|
||||
"unpack",
|
||||
"gcinfo",
|
||||
"pairs",
|
||||
"rawget",
|
||||
"loadstring",
|
||||
"ipairs",
|
||||
"_VERSION",
|
||||
"setfenv",
|
||||
"load",
|
||||
"error",
|
||||
"print", /* should be blocked, but kept in old releases to avoid breaking change. */
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Lua builtins which are not documented on the Lua documentation */
|
||||
static char *lua_builtins_not_documented_allow_list[] = {
|
||||
"newproxy",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Lua builtins which are allowed on initialization but will be removed right after */
|
||||
static char *lua_builtins_removed_after_initialization_allow_list[] = {
|
||||
"debug", /* debug will be set to nil after the error handler will be created */
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Those allow lists was created from the globals that was
|
||||
* available to the user when the allow lists was first introduce.
|
||||
* Because we do not want to break backward compatibility we keep
|
||||
* all the globals. The allow lists will prevent us from accidentally
|
||||
* creating unwanted globals in the future.
|
||||
*
|
||||
* Also notice that the allow list is only checked on start time,
|
||||
* after that the global table is locked so not need to check anything.*/
|
||||
static char **allow_lists[] = {
|
||||
libraries_allow_list,
|
||||
redis_api_allow_list,
|
||||
lua_builtins_allow_list,
|
||||
lua_builtins_not_documented_allow_list,
|
||||
lua_builtins_removed_after_initialization_allow_list,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Deny list contains elements which we know we do not want to add to globals
|
||||
* and there is no need to print a warning message form them. We will print a
|
||||
* log message only if an element was added to the globals and the element is
|
||||
* not on the allow list nor on the back list. */
|
||||
static char *deny_list[] = {
|
||||
"dofile",
|
||||
"loadfile",
|
||||
NULL,
|
||||
};
|
||||
|
||||
char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
|
||||
@@ -128,6 +220,16 @@ void sha1hex(char *digest, char *script, size_t len) {
|
||||
*/
|
||||
|
||||
char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
||||
|
||||
if (!lua_checkstack(lua, 5)) {
|
||||
/*
|
||||
* Increase the Lua stack if needed, to make sure there is enough room
|
||||
* to push 5 elements to the stack. On failure, exit with panic.
|
||||
* Notice that we need, in the worst case, 5 elements because redisProtocolToLuaType_Aggregate
|
||||
* might push 5 elements to the Lua stack.*/
|
||||
serverPanic("lua stack limit reach when parsing redis.call reply");
|
||||
}
|
||||
|
||||
char *p = reply;
|
||||
|
||||
switch(*p) {
|
||||
@@ -220,6 +322,11 @@ char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) {
|
||||
if (atype == '%') {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
} else {
|
||||
if (!lua_checkstack(lua, 1)) {
|
||||
/* Notice that here we need to check the stack again because the recursive
|
||||
* call to redisProtocolToLuaType might have use the room allocated in the stack */
|
||||
serverPanic("lua stack limit reach when parsing redis.call reply");
|
||||
}
|
||||
lua_pushboolean(lua,1);
|
||||
}
|
||||
lua_settable(lua,-3);
|
||||
@@ -313,21 +420,7 @@ void luaSortArray(lua_State *lua) {
|
||||
lua_pushstring(lua,"sort");
|
||||
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
|
||||
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
|
||||
if (lua_pcall(lua,1,0,0)) {
|
||||
/* Stack: array, table, error */
|
||||
|
||||
/* We are not interested in the error, we assume that the problem is
|
||||
* that there are 'false' elements inside the array, so we try
|
||||
* again with a slower function but able to handle this case, that
|
||||
* is: table.sort(table, __redis__compare_helper) */
|
||||
lua_pop(lua,1); /* Stack: array, table */
|
||||
lua_pushstring(lua,"sort"); /* Stack: array, table, sort */
|
||||
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
|
||||
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
|
||||
lua_getglobal(lua,"__redis__compare_helper");
|
||||
/* Stack: array, table, table.sort, array, __redis__compare_helper */
|
||||
lua_call(lua,2,0);
|
||||
}
|
||||
lua_call(lua,1,0); /* Stack: array (sorted), table */
|
||||
/* Stack: array (sorted), table */
|
||||
lua_pop(lua,1); /* Stack: array (sorted) */
|
||||
}
|
||||
@@ -339,6 +432,17 @@ void luaSortArray(lua_State *lua) {
|
||||
/* Reply to client 'c' converting the top element in the Lua stack to a
|
||||
* Redis reply. As a side effect the element is consumed from the stack. */
|
||||
void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
if (!lua_checkstack(lua, 4)) {
|
||||
/* Increase the Lua stack if needed to make sure there is enough room
|
||||
* to push 4 elements to the stack. On failure, return error.
|
||||
* Notice that we need, in the worst case, 4 elements because returning a map might
|
||||
* require push 4 elements to the Lua stack.*/
|
||||
addReplyErrorFormat(c, "reached lua stack limit");
|
||||
lua_pop(lua,1); /* pop the element from the stack */
|
||||
return;
|
||||
}
|
||||
|
||||
int t = lua_type(lua,-1);
|
||||
|
||||
switch(t) {
|
||||
@@ -362,8 +466,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
* field. */
|
||||
|
||||
/* Handle error reply. */
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushstring(lua,"err");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TSTRING) {
|
||||
addReplyErrorFormat(c,"-%s",lua_tostring(lua,-1));
|
||||
@@ -374,7 +479,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle status reply. */
|
||||
lua_pushstring(lua,"ok");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TSTRING) {
|
||||
sds ok = sdsnew(lua_tostring(lua,-1));
|
||||
@@ -388,7 +493,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle double reply. */
|
||||
lua_pushstring(lua,"double");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TNUMBER) {
|
||||
addReplyDouble(c,lua_tonumber(lua,-1));
|
||||
@@ -399,11 +504,12 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle map reply. */
|
||||
lua_pushstring(lua,"map");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TTABLE) {
|
||||
int maplen = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, value */
|
||||
@@ -421,11 +527,12 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
|
||||
/* Handle set reply. */
|
||||
lua_pushstring(lua,"set");
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TTABLE) {
|
||||
int setlen = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, true */
|
||||
@@ -445,8 +552,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int j = 1, mbulklen = 0;
|
||||
while(1) {
|
||||
/* we took care of the stack size on function start */
|
||||
lua_pushnumber(lua,j++);
|
||||
lua_gettable(lua,-2);
|
||||
lua_rawget(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TNIL) {
|
||||
lua_pop(lua,1);
|
||||
@@ -556,6 +664,10 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
return raise_error ? luaRaiseError(lua) : 1;
|
||||
}
|
||||
|
||||
/* Pop all arguments from the stack, we do not need them anymore
|
||||
* and this way we guaranty we will have room on the stack for the result. */
|
||||
lua_pop(lua, argc);
|
||||
|
||||
/* Setup our fake client for command execution */
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
@@ -604,9 +716,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
|
||||
/* Check the ACLs. */
|
||||
int acl_errpos;
|
||||
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos);
|
||||
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
|
||||
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
|
||||
int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
|
||||
if (acl_retval != ACL_OK) {
|
||||
addACLLogEntry(c,acl_retval,acl_errpos,NULL);
|
||||
switch (acl_retval) {
|
||||
@@ -619,7 +729,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
"at least one of the keys mentioned in the "
|
||||
"command arguments");
|
||||
break;
|
||||
case ACL_DENIED_AUTH:
|
||||
case ACL_DENIED_CHANNEL:
|
||||
luaPushError(lua, "The user executing the script can't publish "
|
||||
"to the channel mentioned in the command");
|
||||
break;
|
||||
@@ -657,6 +767,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
sdsfree(aof_write_err);
|
||||
}
|
||||
goto cleanup;
|
||||
} else if (server.masterhost == NULL &&
|
||||
server.repl_min_slaves_max_lag &&
|
||||
server.repl_min_slaves_to_write &&
|
||||
server.repl_good_slaves_count < server.repl_min_slaves_to_write)
|
||||
{
|
||||
luaPushError(lua, shared.noreplicaserr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1041,52 +1158,120 @@ void luaLoadLibraries(lua_State *lua) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Remove a functions that we don't want to expose to the Redis scripting
|
||||
* environment. */
|
||||
void luaRemoveUnsupportedFunctions(lua_State *lua) {
|
||||
lua_pushnil(lua);
|
||||
lua_setglobal(lua,"loadfile");
|
||||
lua_pushnil(lua);
|
||||
lua_setglobal(lua,"dofile");
|
||||
static int luaProtectedTableError(lua_State *lua) {
|
||||
int argc = lua_gettop(lua);
|
||||
if (argc != 2) {
|
||||
serverLog(LL_WARNING, "malicious code trying to call luaProtectedTableError with wrong arguments");
|
||||
luaL_error(lua, "Wrong number of arguments to luaProtectedTableError");
|
||||
}
|
||||
if (!lua_isstring(lua, -1) && !lua_isnumber(lua, -1)) {
|
||||
luaL_error(lua, "Second argument to luaProtectedTableError must be a string or number");
|
||||
}
|
||||
const char *variable_name = lua_tostring(lua, -1);
|
||||
luaL_error(lua, "Script attempted to access nonexistent global variable '%s'", variable_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function installs metamethods in the global table _G that prevent
|
||||
* the creation of globals accidentally.
|
||||
/* Set a special metatable on the table on the top of the stack.
|
||||
* The metatable will raise an error if the user tries to fetch
|
||||
* an un-existing value.
|
||||
*
|
||||
* It should be the last to be called in the scripting engine initialization
|
||||
* sequence, because it may interact with creation of globals. */
|
||||
void scriptingEnableGlobalsProtection(lua_State *lua) {
|
||||
char *s[32];
|
||||
sds code = sdsempty();
|
||||
int j = 0;
|
||||
* The function assumes the Lua stack have a least enough
|
||||
* space to push 2 element, its up to the caller to verify
|
||||
* this before calling this function. */
|
||||
void luaSetErrorMetatable(lua_State *lua) {
|
||||
lua_newtable(lua); /* push metatable */
|
||||
lua_pushcfunction(lua, luaProtectedTableError); /* push get error handler */
|
||||
lua_setfield(lua, -2, "__index");
|
||||
lua_setmetatable(lua, -2);
|
||||
}
|
||||
|
||||
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
|
||||
* Modified to be adapted to Redis. */
|
||||
s[j++]="local dbg=debug\n";
|
||||
s[j++]="local mt = {}\n";
|
||||
s[j++]="setmetatable(_G, mt)\n";
|
||||
s[j++]="mt.__newindex = function (t, n, v)\n";
|
||||
s[j++]=" if dbg.getinfo(2) then\n";
|
||||
s[j++]=" local w = dbg.getinfo(2, \"S\").what\n";
|
||||
s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n";
|
||||
s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
|
||||
s[j++]=" end\n";
|
||||
s[j++]=" end\n";
|
||||
s[j++]=" rawset(t, n, v)\n";
|
||||
s[j++]="end\n";
|
||||
s[j++]="mt.__index = function (t, n)\n";
|
||||
s[j++]=" if dbg.getinfo(2) and dbg.getinfo(2, \"S\").what ~= \"C\" then\n";
|
||||
s[j++]=" error(\"Script attempted to access nonexistent global variable '\"..tostring(n)..\"'\", 2)\n";
|
||||
s[j++]=" end\n";
|
||||
s[j++]=" return rawget(t, n)\n";
|
||||
s[j++]="end\n";
|
||||
s[j++]="debug = nil\n";
|
||||
s[j++]=NULL;
|
||||
/* Set the readonly flag on the table located on the top of the stack
|
||||
* and recursively call this function on each table located on the original
|
||||
* table. Also, recursively call this function on the metatables.*/
|
||||
void luaSetTableProtectionRecursively(lua_State *lua) {
|
||||
/* This protect us from a loop in case we already visited the table
|
||||
* For example, globals has '_G' key which is pointing back to globals. */
|
||||
if (lua_isreadonlytable(lua, -1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
|
||||
luaL_loadbuffer(lua,code,sdslen(code),"@enable_strict_lua");
|
||||
lua_pcall(lua,0,0,0);
|
||||
sdsfree(code);
|
||||
/* protect the current table */
|
||||
lua_enablereadonlytable(lua, -1, 1);
|
||||
|
||||
lua_checkstack(lua, 2);
|
||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||
while (lua_next(lua,-2)) {
|
||||
/* Stack now: table, key, value */
|
||||
if (lua_istable(lua, -1)) {
|
||||
luaSetTableProtectionRecursively(lua);
|
||||
}
|
||||
lua_pop(lua, 1);
|
||||
}
|
||||
|
||||
/* protect the metatable if exists */
|
||||
if (lua_getmetatable(lua, -1)) {
|
||||
luaSetTableProtectionRecursively(lua);
|
||||
lua_pop(lua, 1); /* pop the metatable */
|
||||
}
|
||||
}
|
||||
|
||||
static int luaNewIndexAllowList(lua_State *lua) {
|
||||
int argc = lua_gettop(lua);
|
||||
if (argc != 3) {
|
||||
serverLog(LL_WARNING, "malicious code trying to call luaNewIndexAllowList with wrong arguments");
|
||||
luaL_error(lua, "Wrong number of arguments to luaNewIndexAllowList");
|
||||
}
|
||||
if (!lua_istable(lua, -3)) {
|
||||
luaL_error(lua, "first argument to luaNewIndexAllowList must be a table");
|
||||
}
|
||||
if (!lua_isstring(lua, -2) && !lua_isnumber(lua, -2)) {
|
||||
luaL_error(lua, "Second argument to luaNewIndexAllowList must be a string or number");
|
||||
}
|
||||
const char *variable_name = lua_tostring(lua, -2);
|
||||
/* check if the key is in our allow list */
|
||||
|
||||
char ***allow_l = allow_lists;
|
||||
for (; *allow_l ; ++allow_l){
|
||||
char **c = *allow_l;
|
||||
for (; *c ; ++c) {
|
||||
if (strcmp(*c, variable_name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*c) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!*allow_l) {
|
||||
/* Search the value on the back list, if its there we know that it was removed
|
||||
* on purpose and there is no need to print a warning. */
|
||||
char **c = deny_list;
|
||||
for ( ; *c ; ++c) {
|
||||
if (strcmp(*c, variable_name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!*c) {
|
||||
serverLog(LL_WARNING, "A key '%s' was added to Lua globals which is not on the globals allow list nor listed on the deny list.", variable_name);
|
||||
}
|
||||
} else {
|
||||
lua_rawset(lua, -3);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set a metatable with '__newindex' function that verify that
|
||||
* the new index appears on our globals while list.
|
||||
*
|
||||
* The metatable is set on the table which located on the top
|
||||
* of the stack.
|
||||
*/
|
||||
void luaSetAllowListProtection(lua_State *lua) {
|
||||
lua_newtable(lua); /* push metatable */
|
||||
lua_pushcfunction(lua, luaNewIndexAllowList); /* push get error handler */
|
||||
lua_setfield(lua, -2, "__newindex");
|
||||
lua_setmetatable(lua, -2);
|
||||
}
|
||||
|
||||
/* Initialize the scripting environment.
|
||||
@@ -1110,8 +1295,11 @@ void scriptingInit(int setup) {
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
lua_pushvalue(lua, LUA_GLOBALSINDEX);
|
||||
luaSetAllowListProtection(lua);
|
||||
lua_pop(lua, 1);
|
||||
|
||||
luaLoadLibraries(lua);
|
||||
luaRemoveUnsupportedFunctions(lua);
|
||||
|
||||
/* Initialize a dictionary we use to map SHAs to scripts.
|
||||
* This is useful for replication, as we need to replicate EVALSHA
|
||||
@@ -1227,24 +1415,13 @@ void scriptingInit(int setup) {
|
||||
|
||||
lua_setglobal(lua,"math");
|
||||
|
||||
/* Add a helper function that we use to sort the multi bulk output of non
|
||||
* deterministic commands, when containing 'false' elements. */
|
||||
{
|
||||
char *compare_func = "function __redis__compare_helper(a,b)\n"
|
||||
" if a == false then a = '' end\n"
|
||||
" if b == false then b = '' end\n"
|
||||
" return a<b\n"
|
||||
"end\n";
|
||||
luaL_loadbuffer(lua,compare_func,strlen(compare_func),"@cmp_func_def");
|
||||
lua_pcall(lua,0,0,0);
|
||||
}
|
||||
|
||||
/* Add a helper function we use for pcall error reporting.
|
||||
* Note that when the error is in the C function we want to report the
|
||||
* information about the caller, that's what makes sense from the point
|
||||
* of view of the user debugging a script. */
|
||||
{
|
||||
char *errh_func = "local dbg = debug\n"
|
||||
"debug = nil\n"
|
||||
"function __redis__err__handler(err)\n"
|
||||
" local i = dbg.getinfo(2,'nSl')\n"
|
||||
" if i and i.what == 'C' then\n"
|
||||
@@ -1272,10 +1449,12 @@ void scriptingInit(int setup) {
|
||||
server.lua_client->flags |= CLIENT_DENY_BLOCKING;
|
||||
}
|
||||
|
||||
/* Lua beginners often don't use "local", this is likely to introduce
|
||||
* subtle bugs in their code. To prevent problems we protect accesses
|
||||
* to global variables. */
|
||||
scriptingEnableGlobalsProtection(lua);
|
||||
/* Lock the global table from any changes */
|
||||
lua_pushvalue(lua, LUA_GLOBALSINDEX);
|
||||
luaSetErrorMetatable(lua);
|
||||
/* Recursively lock all tables that can be reached from the global table */
|
||||
luaSetTableProtectionRecursively(lua);
|
||||
lua_pop(lua, 1);
|
||||
|
||||
server.lua = lua;
|
||||
}
|
||||
@@ -1306,7 +1485,9 @@ void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
|
||||
lua_pushlstring(lua,(char*)elev[j]->ptr,sdslen(elev[j]->ptr));
|
||||
lua_rawseti(lua,-2,j+1);
|
||||
}
|
||||
lua_enablereadonlytable(lua, LUA_GLOBALSINDEX, 0);
|
||||
lua_setglobal(lua,var);
|
||||
lua_enablereadonlytable(lua, LUA_GLOBALSINDEX, 1);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
@@ -1387,14 +1568,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
return dictGetKey(de);
|
||||
}
|
||||
|
||||
sds funcdef = sdsempty();
|
||||
funcdef = sdscat(funcdef,"function ");
|
||||
funcdef = sdscatlen(funcdef,funcname,42);
|
||||
funcdef = sdscatlen(funcdef,"() ",3);
|
||||
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
|
||||
funcdef = sdscatlen(funcdef,"\nend",4);
|
||||
|
||||
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
|
||||
if (luaL_loadbuffer(lua,body->ptr,sdslen(body->ptr),"@user_script")) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,
|
||||
"Error compiling script (new function): %s\n",
|
||||
@@ -1402,20 +1576,12 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
sdsfree(sha);
|
||||
sdsfree(funcdef);
|
||||
return NULL;
|
||||
}
|
||||
sdsfree(funcdef);
|
||||
|
||||
if (lua_pcall(lua,0,0,0)) {
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,"Error running script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
sdsfree(sha);
|
||||
return NULL;
|
||||
}
|
||||
serverAssert(lua_isfunction(lua, -1));
|
||||
|
||||
lua_setfield(lua, LUA_REGISTRYINDEX, funcname);
|
||||
|
||||
/* We also save a SHA1 -> Original script map in a dictionary
|
||||
* so that we can replicate / write in the AOF all the
|
||||
@@ -1429,7 +1595,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
|
||||
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
long long elapsed = mstime() - server.lua_time_start;
|
||||
long long elapsed = elapsedMs(server.lua_time_start);
|
||||
UNUSED(ar);
|
||||
UNUSED(lua);
|
||||
|
||||
@@ -1453,6 +1619,14 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
if (server.lua_timedout) processEventsWhileBlocked();
|
||||
if (server.lua_kill) {
|
||||
serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL.");
|
||||
|
||||
/*
|
||||
* Set the hook to invoke all the time so the user
|
||||
* will not be able to catch the error with pcall and invoke
|
||||
* pcall again which will prevent the script from ever been killed
|
||||
*/
|
||||
lua_sethook(lua, luaMaskCountHook, LUA_MASKLINE, 0);
|
||||
|
||||
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
|
||||
lua_error(lua);
|
||||
}
|
||||
@@ -1498,7 +1672,6 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
server.lua_replicate_commands = server.lua_always_replicate_commands;
|
||||
server.lua_multi_emitted = 0;
|
||||
server.lua_repl = PROPAGATE_AOF|PROPAGATE_REPL;
|
||||
server.in_eval = 1;
|
||||
|
||||
/* Get the number of arguments that are keys */
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK)
|
||||
@@ -1536,7 +1709,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
lua_getglobal(lua, "__redis__err__handler");
|
||||
|
||||
/* Try to lookup the Lua function */
|
||||
lua_getglobal(lua, funcname);
|
||||
lua_getfield(lua, LUA_REGISTRYINDEX, funcname);
|
||||
if (lua_isnil(lua,-1)) {
|
||||
lua_pop(lua,1); /* remove the nil from the stack */
|
||||
/* Function not defined... let's define it if we have the
|
||||
@@ -1544,7 +1717,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
* return an error. */
|
||||
if (evalsha) {
|
||||
lua_pop(lua,1); /* remove the error handler from the stack. */
|
||||
addReply(c, shared.noscripterr);
|
||||
addReplyErrorObject(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
if (luaCreateFunction(c,lua,c->argv[1]) == NULL) {
|
||||
@@ -1554,7 +1727,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
return;
|
||||
}
|
||||
/* Now the following is guaranteed to return non nil */
|
||||
lua_getglobal(lua, funcname);
|
||||
lua_getfield(lua, LUA_REGISTRYINDEX, funcname);
|
||||
serverAssert(!lua_isnil(lua,-1));
|
||||
}
|
||||
|
||||
@@ -1570,9 +1743,11 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
*
|
||||
* If we are debugging, we set instead a "line" hook so that the
|
||||
* debugger is call-back at every line executed by the script. */
|
||||
server.in_eval = 1;
|
||||
server.lua_caller = c;
|
||||
server.lua_cur_script = funcname + 2;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_time_start = getMonotonicUs();
|
||||
server.lua_time_snapshot = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
@@ -1602,6 +1777,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
if (server.masterhost && server.master)
|
||||
queueClientForReprocessing(server.master);
|
||||
}
|
||||
server.in_eval = 0;
|
||||
server.lua_caller = NULL;
|
||||
server.lua_cur_script = NULL;
|
||||
|
||||
@@ -1678,11 +1854,12 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
||||
}
|
||||
}
|
||||
|
||||
server.in_eval = 0;
|
||||
}
|
||||
|
||||
void evalCommand(client *c) {
|
||||
/* Explicitly feed monitor here so that lua commands appear after their
|
||||
* script command. */
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
if (!(c->flags & CLIENT_LUA_DEBUG))
|
||||
evalGenericCommand(c,0);
|
||||
else
|
||||
@@ -1690,12 +1867,15 @@ void evalCommand(client *c) {
|
||||
}
|
||||
|
||||
void evalShaCommand(client *c) {
|
||||
/* Explicitly feed monitor here so that lua commands appear after their
|
||||
* script command. */
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
if (sdslen(c->argv[1]->ptr) != 40) {
|
||||
/* We know that a match is not possible if the provided SHA is
|
||||
* not the right length. So we return an error ASAP, this way
|
||||
* evalGenericCommand() can be implemented without string length
|
||||
* sanity check */
|
||||
addReply(c, shared.noscripterr);
|
||||
addReplyErrorObject(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
if (!(c->flags & CLIENT_LUA_DEBUG))
|
||||
@@ -2068,7 +2248,8 @@ int ldbDelBreakpoint(int line) {
|
||||
/* Expect a valid multi-bulk command in the debugging client query buffer.
|
||||
* On success the command is parsed and returned as an array of SDS strings,
|
||||
* otherwise NULL is returned and there is to read more buffer. */
|
||||
sds *ldbReplParseCommand(int *argcp) {
|
||||
sds *ldbReplParseCommand(int *argcp, char** err) {
|
||||
static char* protocol_error = "protocol error";
|
||||
sds *argv = NULL;
|
||||
int argc = 0;
|
||||
if (sdslen(ldb.cbuf) == 0) return NULL;
|
||||
@@ -2085,7 +2266,7 @@ sds *ldbReplParseCommand(int *argcp) {
|
||||
/* Seek and parse *<count>\r\n. */
|
||||
p = strchr(p,'*'); if (!p) goto protoerr;
|
||||
char *plen = p+1; /* Multi bulk len pointer. */
|
||||
p = strstr(p,"\r\n"); if (!p) goto protoerr;
|
||||
p = strstr(p,"\r\n"); if (!p) goto keep_reading;
|
||||
*p = '\0'; p += 2;
|
||||
*argcp = atoi(plen);
|
||||
if (*argcp <= 0 || *argcp > 1024) goto protoerr;
|
||||
@@ -2094,12 +2275,16 @@ sds *ldbReplParseCommand(int *argcp) {
|
||||
argv = zmalloc(sizeof(sds)*(*argcp));
|
||||
argc = 0;
|
||||
while(argc < *argcp) {
|
||||
/* reached the end but there should be more data to read */
|
||||
if (*p == '\0') goto keep_reading;
|
||||
|
||||
if (*p != '$') goto protoerr;
|
||||
plen = p+1; /* Bulk string len pointer. */
|
||||
p = strstr(p,"\r\n"); if (!p) goto protoerr;
|
||||
p = strstr(p,"\r\n"); if (!p) goto keep_reading;
|
||||
*p = '\0'; p += 2;
|
||||
int slen = atoi(plen); /* Length of this arg. */
|
||||
if (slen <= 0 || slen > 1024) goto protoerr;
|
||||
if ((size_t)(p + slen + 2 - copy) > sdslen(copy) ) goto keep_reading;
|
||||
argv[argc++] = sdsnewlen(p,slen);
|
||||
p += slen; /* Skip the already parsed argument. */
|
||||
if (p[0] != '\r' || p[1] != '\n') goto protoerr;
|
||||
@@ -2109,6 +2294,8 @@ sds *ldbReplParseCommand(int *argcp) {
|
||||
return argv;
|
||||
|
||||
protoerr:
|
||||
*err = protocol_error;
|
||||
keep_reading:
|
||||
sdsfreesplitres(argv,argc);
|
||||
sdsfree(copy);
|
||||
return NULL;
|
||||
@@ -2541,6 +2728,17 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||||
void ldbRedis(lua_State *lua, sds *argv, int argc) {
|
||||
int j, saved_rc = server.lua_replicate_commands;
|
||||
|
||||
if (!lua_checkstack(lua, argc + 1)) {
|
||||
/* Increase the Lua stack if needed to make sure there is enough room
|
||||
* to push 'argc + 1' elements to the stack. On failure, return error.
|
||||
* Notice that we need, in worst case, 'argc + 1' elements because we push all the arguments
|
||||
* given by the user (without the first argument) and we also push the 'redis' global table and
|
||||
* 'redis.call' function so:
|
||||
* (1 (redis table)) + (1 (redis.call function)) + (argc - 1 (all arguments without the first)) = argc + 1*/
|
||||
ldbLogRedisReply("max lua stack reached");
|
||||
return;
|
||||
}
|
||||
|
||||
lua_getglobal(lua,"redis");
|
||||
lua_pushstring(lua,"call");
|
||||
lua_gettable(lua,-2); /* Stack: redis, redis.call */
|
||||
@@ -2597,12 +2795,17 @@ void ldbMaxlen(sds *argv, int argc) {
|
||||
int ldbRepl(lua_State *lua) {
|
||||
sds *argv;
|
||||
int argc;
|
||||
char* err = NULL;
|
||||
|
||||
/* We continue processing commands until a command that should return
|
||||
* to the Lua interpreter is found. */
|
||||
while(1) {
|
||||
while((argv = ldbReplParseCommand(&argc)) == NULL) {
|
||||
while((argv = ldbReplParseCommand(&argc, &err)) == NULL) {
|
||||
char buf[1024];
|
||||
if (err) {
|
||||
lua_pushstring(lua, err);
|
||||
lua_error(lua);
|
||||
}
|
||||
int nread = connRead(ldb.conn,buf,sizeof(buf));
|
||||
if (nread <= 0) {
|
||||
/* Make sure the script runs without user input since the
|
||||
@@ -2612,6 +2815,15 @@ int ldbRepl(lua_State *lua) {
|
||||
return C_ERR;
|
||||
}
|
||||
ldb.cbuf = sdscatlen(ldb.cbuf,buf,nread);
|
||||
/* after 1M we will exit with an error
|
||||
* so that the client will not blow the memory
|
||||
*/
|
||||
if (sdslen(ldb.cbuf) > 1<<20) {
|
||||
sdsfree(ldb.cbuf);
|
||||
ldb.cbuf = sdsempty();
|
||||
lua_pushstring(lua, "max client buffer reached");
|
||||
lua_error(lua);
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the old buffer. */
|
||||
@@ -2724,7 +2936,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||
|
||||
/* Check if a timeout occurred. */
|
||||
if (ar->event == LUA_HOOKCOUNT && ldb.step == 0 && bp == 0) {
|
||||
mstime_t elapsed = mstime() - server.lua_time_start;
|
||||
mstime_t elapsed = elapsedMs(server.lua_time_start);
|
||||
mstime_t timelimit = server.lua_time_limit ?
|
||||
server.lua_time_limit : 5000;
|
||||
if (elapsed >= timelimit) {
|
||||
@@ -2754,6 +2966,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||
lua_pushstring(lua, "timeout during Lua debugging with client closing connection");
|
||||
lua_error(lua);
|
||||
}
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_time_start = getMonotonicUs();
|
||||
server.lua_time_snapshot = mstime();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ void sdsclear(sds s) {
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
void *sh, *newsh;
|
||||
size_t avail = sdsavail(s);
|
||||
size_t len, newlen;
|
||||
size_t len, newlen, reqlen;
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen;
|
||||
size_t usable;
|
||||
@@ -243,7 +243,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
|
||||
len = sdslen(s);
|
||||
sh = (char*)s-sdsHdrSize(oldtype);
|
||||
newlen = (len+addlen);
|
||||
reqlen = newlen = (len+addlen);
|
||||
assert(newlen > len); /* Catch size_t overflow */
|
||||
if (newlen < SDS_MAX_PREALLOC)
|
||||
newlen *= 2;
|
||||
@@ -258,7 +258,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
|
||||
|
||||
hdrlen = sdsHdrSize(type);
|
||||
assert(hdrlen + newlen + 1 > len); /* Catch size_t overflow */
|
||||
assert(hdrlen + newlen + 1 > reqlen); /* Catch size_t overflow */
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
|
||||
if (newsh == NULL) return NULL;
|
||||
@@ -287,40 +287,72 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdsRemoveFreeSpace(sds s) {
|
||||
sds sdsRemoveFreeSpace(sds s, int would_regrow) {
|
||||
return sdsResize(s, sdslen(s), would_regrow);
|
||||
}
|
||||
|
||||
/* Resize the allocation, this can make the allocation bigger or smaller,
|
||||
* if the size is smaller than currently used len, the data will be truncated.
|
||||
*
|
||||
* The when the would_regrow argument is set to 1, it prevents the use of
|
||||
* SDS_TYPE_5, which is desired when the sds is likely to be changed again.
|
||||
*
|
||||
* The sdsAlloc size will be set to the requested size regardless of the actual
|
||||
* allocation size, this is done in order to avoid repeated calls to this
|
||||
* function when the caller detects that it has excess space. */
|
||||
sds sdsResize(sds s, size_t size, int would_regrow) {
|
||||
void *sh, *newsh;
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
|
||||
size_t len = sdslen(s);
|
||||
size_t avail = sdsavail(s);
|
||||
sh = (char*)s-oldhdrlen;
|
||||
|
||||
/* Return ASAP if there is no space left. */
|
||||
if (avail == 0) return s;
|
||||
/* Return ASAP if the size is already good. */
|
||||
if (sdsalloc(s) == size) return s;
|
||||
|
||||
/* Truncate len if needed. */
|
||||
if (size < len) len = size;
|
||||
|
||||
/* Check what would be the minimum SDS header that is just good enough to
|
||||
* fit this string. */
|
||||
type = sdsReqType(len);
|
||||
type = sdsReqType(size);
|
||||
if (would_regrow) {
|
||||
/* Don't use type 5, it is not good for strings that are expected to grow back. */
|
||||
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
|
||||
}
|
||||
hdrlen = sdsHdrSize(type);
|
||||
|
||||
/* If the type is the same, or at least a large enough type is still
|
||||
* required, we just realloc(), letting the allocator to do the copy
|
||||
* only if really needed. Otherwise if the change is huge, we manually
|
||||
* reallocate the string to use the different header type. */
|
||||
if (oldtype==type || type > SDS_TYPE_8) {
|
||||
newsh = s_realloc(sh, oldhdrlen+len+1);
|
||||
/* If the type is the same, or can hold the size in it with low overhead
|
||||
* (larger than SDS_TYPE_8), we just realloc(), letting the allocator
|
||||
* to do the copy only if really needed. Otherwise if the change is
|
||||
* huge, we manually reallocate the string to use the different header
|
||||
* type. */
|
||||
int use_realloc = (oldtype==type || (type < oldtype && type > SDS_TYPE_8));
|
||||
size_t newlen = use_realloc ? oldhdrlen+size+1 : hdrlen+size+1;
|
||||
int alloc_already_optimal = 0;
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* je_nallocx returns the expected allocation size for the newlen.
|
||||
* We aim to avoid calling realloc() when using Jemalloc if there is no
|
||||
* change in the allocation size, as it incurs a cost even if the
|
||||
* allocation size stays the same. */
|
||||
alloc_already_optimal = (je_nallocx(newlen, 0) == zmalloc_size(sh));
|
||||
#endif
|
||||
|
||||
if (use_realloc && !alloc_already_optimal) {
|
||||
newsh = s_realloc(sh, newlen);
|
||||
if (newsh == NULL) return NULL;
|
||||
s = (char*)newsh+oldhdrlen;
|
||||
} else {
|
||||
newsh = s_malloc(hdrlen+len+1);
|
||||
} else if (!alloc_already_optimal) {
|
||||
newsh = s_malloc(newlen);
|
||||
if (newsh == NULL) return NULL;
|
||||
memcpy((char*)newsh+hdrlen, s, len+1);
|
||||
memcpy((char*)newsh+hdrlen, s, len);
|
||||
s_free(sh);
|
||||
s = (char*)newsh+hdrlen;
|
||||
s[-1] = type;
|
||||
sdssetlen(s, len);
|
||||
}
|
||||
sdssetalloc(s, len);
|
||||
s[len] = 0;
|
||||
sdssetlen(s, len);
|
||||
sdssetalloc(s, size);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -759,6 +791,21 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Changes the input string to be a subset of the original.
|
||||
* It does not release the free space in the string, so a call to
|
||||
* sdsRemoveFreeSpace may be wise after. */
|
||||
void sdssubstr(sds s, size_t start, size_t len) {
|
||||
/* Clamp out of range input */
|
||||
size_t oldlen = sdslen(s);
|
||||
if (start >= oldlen) start = len = 0;
|
||||
if (len > oldlen-start) len = oldlen-start;
|
||||
|
||||
/* Move the data */
|
||||
if (len) memmove(s, s+start, len);
|
||||
s[len] = 0;
|
||||
sdssetlen(s,len);
|
||||
}
|
||||
|
||||
/* Turn the string into a smaller (or equal) string containing only the
|
||||
* substring specified by the 'start' and 'end' indexes.
|
||||
*
|
||||
@@ -770,6 +817,11 @@ sds sdstrim(sds s, const char *cset) {
|
||||
*
|
||||
* The string is modified in-place.
|
||||
*
|
||||
* NOTE: this function can be misleading and can have unexpected behaviour,
|
||||
* specifically when you want the length of the new string to be 0.
|
||||
* Having start==end will result in a string with one character.
|
||||
* please consider using sdssubstr instead.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("Hello World");
|
||||
@@ -777,28 +829,13 @@ sds sdstrim(sds s, const char *cset) {
|
||||
*/
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end) {
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return;
|
||||
if (start < 0) {
|
||||
start = len+start;
|
||||
if (start < 0) start = 0;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = len+end;
|
||||
if (end < 0) end = 0;
|
||||
}
|
||||
if (start < 0)
|
||||
start = len + start;
|
||||
if (end < 0)
|
||||
end = len + end;
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
if (newlen != 0) {
|
||||
if (start >= (ssize_t)len) {
|
||||
newlen = 0;
|
||||
} else if (end >= (ssize_t)len) {
|
||||
end = len-1;
|
||||
newlen = (start > end) ? 0 : (end-start)+1;
|
||||
}
|
||||
}
|
||||
if (start && newlen) memmove(s, s+start, newlen);
|
||||
s[newlen] = 0;
|
||||
sdssetlen(s,newlen);
|
||||
sdssubstr(s, start, newlen);
|
||||
}
|
||||
|
||||
/* Apply tolower() to every character of the sds string 's'. */
|
||||
@@ -1234,9 +1271,10 @@ static sds sdsTestTemplateCallback(sds varname, void *arg) {
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
int sdsTest(int argc, char **argv) {
|
||||
int sdsTest(int argc, char **argv, int accurate) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(accurate);
|
||||
|
||||
{
|
||||
sds x = sdsnew("foo"), y;
|
||||
@@ -1352,6 +1390,18 @@ int sdsTest(int argc, char **argv) {
|
||||
test_cond("sdsrange(...,100,100)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,4,6);
|
||||
test_cond("sdsrange(...,4,6)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,3,6);
|
||||
test_cond("sdsrange(...,3,6)",
|
||||
sdslen(y) == 1 && memcmp(y,"o\0",2) == 0);
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnew("foo");
|
||||
@@ -1437,6 +1487,34 @@ int sdsTest(int argc, char **argv) {
|
||||
test_cond("sdstemplate() with quoting",
|
||||
memcmp(x,"v1={value1} {} v2=value2",24) == 0);
|
||||
sdsfree(x);
|
||||
|
||||
/* Test sdsresize - extend */
|
||||
x = sdsnew("1234567890123456789012345678901234567890");
|
||||
x = sdsResize(x, 200, 1);
|
||||
test_cond("sdsrezie() expand len", sdslen(x) == 40);
|
||||
test_cond("sdsrezie() expand strlen", strlen(x) == 40);
|
||||
test_cond("sdsrezie() expand alloc", sdsalloc(x) == 200);
|
||||
/* Test sdsresize - trim free space */
|
||||
x = sdsResize(x, 80, 1);
|
||||
test_cond("sdsrezie() shrink len", sdslen(x) == 40);
|
||||
test_cond("sdsrezie() shrink strlen", strlen(x) == 40);
|
||||
test_cond("sdsrezie() shrink alloc", sdsalloc(x) == 80);
|
||||
/* Test sdsresize - crop used space */
|
||||
x = sdsResize(x, 30, 1);
|
||||
test_cond("sdsrezie() crop len", sdslen(x) == 30);
|
||||
test_cond("sdsrezie() crop strlen", strlen(x) == 30);
|
||||
test_cond("sdsrezie() crop alloc", sdsalloc(x) == 30);
|
||||
/* Test sdsresize - extend to different class */
|
||||
x = sdsResize(x, 400, 1);
|
||||
test_cond("sdsrezie() expand len", sdslen(x) == 30);
|
||||
test_cond("sdsrezie() expand strlen", strlen(x) == 30);
|
||||
test_cond("sdsrezie() expand alloc", sdsalloc(x) == 400);
|
||||
/* Test sdsresize - shrink to different class */
|
||||
x = sdsResize(x, 4, 1);
|
||||
test_cond("sdsrezie() crop len", sdslen(x) == 4);
|
||||
test_cond("sdsrezie() crop strlen", strlen(x) == 4);
|
||||
test_cond("sdsrezie() crop alloc", sdsalloc(x) == 4);
|
||||
sdsfree(x);
|
||||
}
|
||||
test_report();
|
||||
return 0;
|
||||
|
||||
@@ -238,6 +238,7 @@ sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
|
||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdssubstr(sds s, size_t start, size_t len);
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
@@ -264,7 +265,8 @@ sds sdstemplate(const char *template, sdstemplate_callback_t cb_func, void *cb_a
|
||||
/* Low level functions exposed to the user API */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen);
|
||||
void sdsIncrLen(sds s, ssize_t incr);
|
||||
sds sdsRemoveFreeSpace(sds s);
|
||||
sds sdsRemoveFreeSpace(sds s, int would_regrow);
|
||||
sds sdsResize(sds s, size_t size, int would_regrow);
|
||||
size_t sdsAllocSize(sds s);
|
||||
void *sdsAllocPtr(sds s);
|
||||
|
||||
@@ -277,7 +279,7 @@ void *sds_realloc(void *ptr, size_t size);
|
||||
void sds_free(void *ptr);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int sdsTest(int argc, char *argv[]);
|
||||
int sdsTest(int argc, char *argv[], int accurate);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+124
-73
@@ -215,6 +215,7 @@ typedef struct sentinelRedisInstance {
|
||||
/* Slave specific. */
|
||||
mstime_t master_link_down_time; /* Slave replication link down time. */
|
||||
int slave_priority; /* Slave priority according to its INFO output. */
|
||||
int replica_announced; /* Replica announcing according to its INFO output. */
|
||||
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */
|
||||
struct sentinelRedisInstance *master; /* Master instance if it's slave. */
|
||||
char *slave_master_host; /* Master host as reported by INFO */
|
||||
@@ -548,14 +549,12 @@ void initSentinel(void) {
|
||||
server.sentinel_config = NULL;
|
||||
}
|
||||
|
||||
/* This function gets called when the server is in Sentinel mode, started,
|
||||
* loaded the configuration, and is ready for normal operations. */
|
||||
void sentinelIsRunning(void) {
|
||||
int j;
|
||||
|
||||
/* This function is for checking whether sentinel config file has been set,
|
||||
* also checking whether we have write permissions. */
|
||||
void sentinelCheckConfigFile(void) {
|
||||
if (server.configfile == NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Sentinel started without a config file. Exiting...");
|
||||
"Sentinel needs config file on disk to save state. Exiting...");
|
||||
exit(1);
|
||||
} else if (access(server.configfile,W_OK) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
@@ -563,6 +562,12 @@ void sentinelIsRunning(void) {
|
||||
server.configfile,strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function gets called when the server is in Sentinel mode, started,
|
||||
* loaded the configuration, and is ready for normal operations. */
|
||||
void sentinelIsRunning(void) {
|
||||
int j;
|
||||
|
||||
/* If this Sentinel has yet no ID set in the configuration file, we
|
||||
* pick a random one and persist the config on disk. From now on this
|
||||
@@ -588,10 +593,10 @@ void sentinelIsRunning(void) {
|
||||
|
||||
/* Create a sentinelAddr object and return it on success.
|
||||
* On error NULL is returned and errno is set to:
|
||||
* ENOENT: Can't resolve the hostname.
|
||||
* ENOENT: Can't resolve the hostname, unless accept_unresolved is non-zero.
|
||||
* EINVAL: Invalid port number.
|
||||
*/
|
||||
sentinelAddr *createSentinelAddr(char *hostname, int port) {
|
||||
sentinelAddr *createSentinelAddr(char *hostname, int port, int is_accept_unresolved) {
|
||||
char ip[NET_IP_STR_LEN];
|
||||
sentinelAddr *sa;
|
||||
|
||||
@@ -601,8 +606,14 @@ sentinelAddr *createSentinelAddr(char *hostname, int port) {
|
||||
}
|
||||
if (anetResolve(NULL,hostname,ip,sizeof(ip),
|
||||
sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
serverLog(LL_WARNING, "Failed to resolve hostname '%s'", hostname);
|
||||
if (sentinel.resolve_hostnames && is_accept_unresolved) {
|
||||
ip[0] = '\0';
|
||||
}
|
||||
else {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
sa = zmalloc(sizeof(*sa));
|
||||
sa->hostname = sdsnew(hostname);
|
||||
@@ -629,19 +640,29 @@ void releaseSentinelAddr(sentinelAddr *sa) {
|
||||
zfree(sa);
|
||||
}
|
||||
|
||||
/* Return non-zero if two addresses are equal. */
|
||||
int sentinelAddrIsEqual(sentinelAddr *a, sentinelAddr *b) {
|
||||
return a->port == b->port && !strcasecmp(a->ip,b->ip);
|
||||
/* Return non-zero if the two addresses are equal, either by address
|
||||
* or by hostname if they could not have been resolved.
|
||||
*/
|
||||
int sentinelAddrOrHostnameEqual(sentinelAddr *a, sentinelAddr *b) {
|
||||
return a->port == b->port &&
|
||||
(!strcmp(a->ip, b->ip) ||
|
||||
!strcasecmp(a->hostname, b->hostname));
|
||||
}
|
||||
|
||||
/* Return non-zero if a hostname matches an address. */
|
||||
int sentinelAddrEqualsHostname(sentinelAddr *a, char *hostname) {
|
||||
char ip[NET_IP_STR_LEN];
|
||||
|
||||
/* We always resolve the hostname and compare it to the address */
|
||||
/* Try resolve the hostname and compare it to the address */
|
||||
if (anetResolve(NULL, hostname, ip, sizeof(ip),
|
||||
sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR)
|
||||
return 0;
|
||||
sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) {
|
||||
|
||||
/* If failed resolve then compare based on hostnames. That is our best effort as
|
||||
* long as the server is unavailable for some reason. It is fine since Redis
|
||||
* instance cannot have multiple hostnames for a given setup */
|
||||
return !strcasecmp(sentinel.resolve_hostnames ? a->hostname : a->ip, hostname);
|
||||
}
|
||||
/* Compare based on address */
|
||||
return !strcasecmp(a->ip, ip);
|
||||
}
|
||||
|
||||
@@ -870,6 +891,7 @@ void sentinelRunPendingScripts(void) {
|
||||
sj->pid = 0;
|
||||
} else if (pid == 0) {
|
||||
/* Child */
|
||||
tlsCleanup();
|
||||
execve(sj->argv[0],sj->argv,environ);
|
||||
/* If we are here an error occurred. */
|
||||
_exit(2); /* Don't retry execution. */
|
||||
@@ -903,7 +925,7 @@ void sentinelCollectTerminatedScripts(void) {
|
||||
int statloc;
|
||||
pid_t pid;
|
||||
|
||||
while ((pid = wait3(&statloc,WNOHANG,NULL)) > 0) {
|
||||
while ((pid = waitpid(-1, &statloc, WNOHANG)) > 0) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = 0;
|
||||
listNode *ln;
|
||||
@@ -915,7 +937,7 @@ void sentinelCollectTerminatedScripts(void) {
|
||||
|
||||
ln = sentinelGetScriptListNodeByPid(pid);
|
||||
if (ln == NULL) {
|
||||
serverLog(LL_WARNING,"wait3() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
|
||||
serverLog(LL_WARNING,"waitpid() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
|
||||
continue;
|
||||
}
|
||||
sj = ln->value;
|
||||
@@ -1290,7 +1312,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
serverAssert((flags & SRI_MASTER) || master != NULL);
|
||||
|
||||
/* Check address validity. */
|
||||
addr = createSentinelAddr(hostname,port);
|
||||
addr = createSentinelAddr(hostname,port,1);
|
||||
if (addr == NULL) return NULL;
|
||||
|
||||
/* For slaves use ip/host:port as name. */
|
||||
@@ -1334,6 +1356,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->auth_pass = NULL;
|
||||
ri->auth_user = NULL;
|
||||
ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;
|
||||
ri->replica_announced = 1;
|
||||
ri->slave_reconf_sent_time = 0;
|
||||
ri->slave_master_host = NULL;
|
||||
ri->slave_master_port = 0;
|
||||
@@ -1417,7 +1440,7 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
|
||||
* If that is the case, depending on configuration we either resolve
|
||||
* it and use the IP addres or fail.
|
||||
*/
|
||||
addr = createSentinelAddr(slave_addr, port);
|
||||
addr = createSentinelAddr(slave_addr, port, 0);
|
||||
if (!addr) return NULL;
|
||||
key = announceSentinelAddrAndPort(addr);
|
||||
releaseSentinelAddr(addr);
|
||||
@@ -1480,8 +1503,10 @@ sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, c
|
||||
|
||||
serverAssert(addr || runid); /* User must pass at least one search param. */
|
||||
if (addr != NULL) {
|
||||
/* Resolve addr, we use the IP as a key even if a hostname is used */
|
||||
ri_addr = createSentinelAddr(addr, port);
|
||||
/* Try to resolve addr. If hostnames are used, we're accepting an ri_addr
|
||||
* that contains an hostname only and can still be matched based on that.
|
||||
*/
|
||||
ri_addr = createSentinelAddr(addr,port,1);
|
||||
if (!ri_addr) return NULL;
|
||||
}
|
||||
di = dictGetIterator(instances);
|
||||
@@ -1490,8 +1515,7 @@ sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, c
|
||||
|
||||
if (runid && !ri->runid) continue;
|
||||
if ((runid == NULL || strcmp(ri->runid, runid) == 0) &&
|
||||
(addr == NULL || (strcmp(ri->addr->ip, ri_addr->ip) == 0 &&
|
||||
ri->addr->port == port)))
|
||||
(addr == NULL || sentinelAddrOrHostnameEqual(ri->addr, ri_addr)))
|
||||
{
|
||||
instance = ri;
|
||||
break;
|
||||
@@ -1622,7 +1646,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hos
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
newaddr = createSentinelAddr(hostname,port);
|
||||
newaddr = createSentinelAddr(hostname,port,0);
|
||||
if (newaddr == NULL) return C_ERR;
|
||||
|
||||
/* There can be only 0 or 1 slave that has the newaddr.
|
||||
@@ -1635,7 +1659,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hos
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *slave = dictGetVal(de);
|
||||
|
||||
if (sentinelAddrIsEqual(slave->addr,newaddr)) continue;
|
||||
if (sentinelAddrOrHostnameEqual(slave->addr,newaddr)) continue;
|
||||
slaves[numslaves++] = dupSentinelAddr(slave->addr);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
@@ -1643,7 +1667,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hos
|
||||
/* If we are switching to a different address, include the old address
|
||||
* as a slave as well, so that we'll be able to sense / reconfigure
|
||||
* the old master. */
|
||||
if (!sentinelAddrIsEqual(newaddr,master->addr)) {
|
||||
if (!sentinelAddrOrHostnameEqual(newaddr,master->addr)) {
|
||||
slaves[numslaves++] = dupSentinelAddr(master->addr);
|
||||
}
|
||||
|
||||
@@ -1719,19 +1743,12 @@ void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) {
|
||||
}
|
||||
}
|
||||
|
||||
char *sentinelGetInstanceTypeString(sentinelRedisInstance *ri) {
|
||||
if (ri->flags & SRI_MASTER) return "master";
|
||||
else if (ri->flags & SRI_SLAVE) return "slave";
|
||||
else if (ri->flags & SRI_SENTINEL) return "sentinel";
|
||||
else return "unknown";
|
||||
}
|
||||
|
||||
/* This function is used in order to send commands to Redis instances: the
|
||||
* commands we send from Sentinel may be renamed, a common case is a master
|
||||
* with CONFIG and SLAVEOF commands renamed for security concerns. In that
|
||||
* case we check the ri->renamed_command table (or if the instance is a slave,
|
||||
* we check the one of the master), and map the command that we should send
|
||||
* to the set of renamed commads. However, if the command was not renamed,
|
||||
* to the set of renamed commands. However, if the command was not renamed,
|
||||
* we just return "command" itself. */
|
||||
char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, char *command) {
|
||||
sds sc = sdsnew(command);
|
||||
@@ -2045,12 +2062,12 @@ const char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
} else if (!strcasecmp(argv[0],"resolve-hostnames") && argc == 2) {
|
||||
/* resolve-hostnames <yes|no> */
|
||||
if ((sentinel.resolve_hostnames = yesnotoi(argv[1])) == -1) {
|
||||
return "Please specify yes or not for the resolve-hostnames option.";
|
||||
return "Please specify yes or no for the resolve-hostnames option.";
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"announce-hostnames") && argc == 2) {
|
||||
/* announce-hostnames <yes|no> */
|
||||
if ((sentinel.announce_hostnames = yesnotoi(argv[1])) == -1) {
|
||||
return "Please specify yes or not for the announce-hostnames option.";
|
||||
return "Please specify yes or no for the announce-hostnames option.";
|
||||
}
|
||||
} else {
|
||||
return "Unrecognized sentinel configuration statement.";
|
||||
@@ -2199,7 +2216,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
* slave's address, a failover is in progress and the slave was
|
||||
* already successfully promoted. So as the address of this slave
|
||||
* we use the old master address instead. */
|
||||
if (sentinelAddrIsEqual(slave_addr,master_addr))
|
||||
if (sentinelAddrOrHostnameEqual(slave_addr,master_addr))
|
||||
slave_addr = master->addr;
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel known-replica %s %s %d",
|
||||
@@ -2320,8 +2337,8 @@ void sentinelFlushConfig(void) {
|
||||
return;
|
||||
|
||||
werr:
|
||||
if (fd != -1) close(fd);
|
||||
serverLog(LL_WARNING,"WARNING: Sentinel was not able to save the new configuration on disk!!!: %s", strerror(errno));
|
||||
if (fd != -1) close(fd);
|
||||
}
|
||||
|
||||
/* ====================== hiredis connection handling ======================= */
|
||||
@@ -2402,7 +2419,10 @@ static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
|
||||
SSL *ssl = SSL_new(redis_tls_client_ctx ? redis_tls_client_ctx : redis_tls_ctx);
|
||||
if (!ssl) return C_ERR;
|
||||
|
||||
if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) return C_ERR;
|
||||
if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) {
|
||||
SSL_free(ssl);
|
||||
return C_ERR;
|
||||
}
|
||||
#endif
|
||||
return C_OK;
|
||||
}
|
||||
@@ -2411,6 +2431,7 @@ static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
|
||||
* is disconnected. Note that link->disconnected is true even if just
|
||||
* one of the two links (commands and pub/sub) is missing. */
|
||||
void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
|
||||
if (ri->link->disconnected == 0) return;
|
||||
if (ri->addr->port == 0) return; /* port == 0 means invalid address. */
|
||||
instanceLink *link = ri->link;
|
||||
@@ -2421,9 +2442,25 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
|
||||
/* Commands connection. */
|
||||
if (link->cc == NULL) {
|
||||
|
||||
/* It might be that the instance is disconnected because it wasn't available earlier when the instance
|
||||
* allocated, say during failover, and therefore we failed to resolve its ip.
|
||||
* Another scenario is that the instance restarted with new ip, and we should resolve its new ip based on
|
||||
* its hostname */
|
||||
if (sentinel.resolve_hostnames) {
|
||||
sentinelAddr *tryResolveAddr = createSentinelAddr(ri->addr->hostname, ri->addr->port, 0);
|
||||
if (tryResolveAddr != NULL) {
|
||||
releaseSentinelAddr(ri->addr);
|
||||
ri->addr = tryResolveAddr;
|
||||
}
|
||||
}
|
||||
|
||||
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
|
||||
if (!link->cc->err) anetCloexec(link->cc->c.fd);
|
||||
if (!link->cc->err && server.tls_replication &&
|
||||
|
||||
if (link->cc && !link->cc->err) anetCloexec(link->cc->c.fd);
|
||||
if (!link->cc) {
|
||||
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to establish connection");
|
||||
} else if (!link->cc->err && server.tls_replication &&
|
||||
(instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
|
||||
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
|
||||
instanceLinkCloseConnection(link,link->cc);
|
||||
@@ -2450,8 +2487,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
/* Pub / Sub */
|
||||
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
|
||||
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
|
||||
if (!link->pc->err) anetCloexec(link->pc->c.fd);
|
||||
if (!link->pc->err && server.tls_replication &&
|
||||
if (link->pc && !link->pc->err) anetCloexec(link->pc->c.fd);
|
||||
if (!link->pc) {
|
||||
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to establish connection");
|
||||
} else if (!link->pc->err && server.tls_replication &&
|
||||
(instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
|
||||
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
|
||||
} else if (link->pc->err) {
|
||||
@@ -2624,6 +2663,10 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
/* slave_repl_offset:<offset> */
|
||||
if (sdslen(l) >= 18 && !memcmp(l,"slave_repl_offset:",18))
|
||||
ri->slave_repl_offset = strtoull(l+18,NULL,10);
|
||||
|
||||
/* replica_announced:<announcement> */
|
||||
if (sdslen(l) >= 18 && !memcmp(l,"replica_announced:",18))
|
||||
ri->replica_announced = atoi(l+18);
|
||||
}
|
||||
}
|
||||
ri->info_refresh = mstime();
|
||||
@@ -2644,8 +2687,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
((ri->flags & (SRI_MASTER|SRI_SLAVE)) == role) ?
|
||||
"+role-change" : "-role-change",
|
||||
ri, "%@ new reported role is %s",
|
||||
role == SRI_MASTER ? "master" : "slave",
|
||||
ri->flags & SRI_MASTER ? "master" : "slave");
|
||||
role == SRI_MASTER ? "master" : "slave");
|
||||
}
|
||||
|
||||
/* None of the following conditions are processed when in tilt mode, so
|
||||
@@ -3168,11 +3210,13 @@ void sentinelConfigSetCommand(client *c) {
|
||||
sentinel.announce_port = numval;
|
||||
} else if (!strcasecmp(o->ptr, "sentinel-user")) {
|
||||
sdsfree(sentinel.sentinel_auth_user);
|
||||
sentinel.sentinel_auth_user = sdsnew(val->ptr);
|
||||
sentinel.sentinel_auth_user = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else if (!strcasecmp(o->ptr, "sentinel-pass")) {
|
||||
sdsfree(sentinel.sentinel_auth_pass);
|
||||
sentinel.sentinel_auth_pass = sdsnew(val->ptr);
|
||||
sentinel.sentinel_auth_pass = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Invalid argument '%s' to SENTINEL CONFIG SET",
|
||||
@@ -3291,6 +3335,8 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
if (ri->flags & SRI_RECONF_SENT) flags = sdscat(flags,"reconf_sent,");
|
||||
if (ri->flags & SRI_RECONF_INPROG) flags = sdscat(flags,"reconf_inprog,");
|
||||
if (ri->flags & SRI_RECONF_DONE) flags = sdscat(flags,"reconf_done,");
|
||||
if (ri->flags & SRI_FORCE_FAILOVER) flags = sdscat(flags,"force_failover,");
|
||||
if (ri->flags & SRI_SCRIPT_KILL_SENT) flags = sdscat(flags,"script_kill_sent,");
|
||||
|
||||
if (sdslen(flags) != 0) sdsrange(flags,0,-2); /* remove last "," */
|
||||
addReplyBulkCString(c,flags);
|
||||
@@ -3343,7 +3389,8 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
/* Masters and Slaves */
|
||||
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
|
||||
addReplyBulkCString(c,"info-refresh");
|
||||
addReplyBulkLongLong(c,mstime() - ri->info_refresh);
|
||||
addReplyBulkLongLong(c,
|
||||
ri->info_refresh ? (mstime() - ri->info_refresh) : 0);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"role-reported");
|
||||
@@ -3423,6 +3470,10 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
addReplyBulkCString(c,"slave-repl-offset");
|
||||
addReplyBulkLongLong(c,ri->slave_repl_offset);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"replica-announced");
|
||||
addReplyBulkLongLong(c,ri->replica_announced);
|
||||
fields++;
|
||||
}
|
||||
|
||||
/* Only sentinels */
|
||||
@@ -3448,15 +3499,20 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
void addReplyDictOfRedisInstances(client *c, dict *instances) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
long slaves = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
di = dictGetIterator(instances);
|
||||
addReplyArrayLen(c,dictSize(instances));
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
|
||||
/* don't announce unannounced replicas */
|
||||
if (ri->flags & SRI_SLAVE && !ri->replica_announced) continue;
|
||||
addReplySentinelRedisInstance(c,ri);
|
||||
slaves++;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c, replylen, slaves);
|
||||
}
|
||||
|
||||
/* Lookup the named master into sentinel.masters.
|
||||
@@ -3663,11 +3719,11 @@ NULL
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2])) == NULL)
|
||||
return;
|
||||
if (ri->flags & SRI_FAILOVER_IN_PROGRESS) {
|
||||
addReplySds(c,sdsnew("-INPROG Failover already in progress\r\n"));
|
||||
addReplyError(c,"-INPROG Failover already in progress");
|
||||
return;
|
||||
}
|
||||
if (sentinelSelectSlave(ri) == NULL) {
|
||||
addReplySds(c,sdsnew("-NOGOODSLAVE No suitable replica to promote\r\n"));
|
||||
addReplyError(c,"-NOGOODSLAVE No suitable replica to promote");
|
||||
return;
|
||||
}
|
||||
serverLog(LL_WARNING,"Executing user requested FAILOVER of '%s'",
|
||||
@@ -3710,17 +3766,7 @@ NULL
|
||||
ri = createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
|
||||
c->argv[3]->ptr,port,quorum,NULL);
|
||||
if (ri == NULL) {
|
||||
switch(errno) {
|
||||
case EBUSY:
|
||||
addReplyError(c,"Duplicated master name");
|
||||
break;
|
||||
case EINVAL:
|
||||
addReplyError(c,"Invalid port number");
|
||||
break;
|
||||
default:
|
||||
addReplyError(c,"Unspecified error adding the instance");
|
||||
break;
|
||||
}
|
||||
addReplyError(c,sentinelCheckCreateInstanceErrors(SRI_MASTER));
|
||||
} else {
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
@@ -3766,8 +3812,7 @@ NULL
|
||||
e = sdscat(e, "Not enough available Sentinels to reach the"
|
||||
" majority and authorize a failover");
|
||||
}
|
||||
e = sdscat(e,"\r\n");
|
||||
addReplySds(c,e);
|
||||
addReplyErrorSds(c,e);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc < 3) goto numargserr;
|
||||
@@ -3820,7 +3865,8 @@ NULL
|
||||
addReplyBulkCBuffer(c,ri->name,strlen(ri->name));
|
||||
addReplyArrayLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c, now - ri->info_refresh);
|
||||
addReplyLongLong(c,
|
||||
ri->info_refresh ? (now - ri->info_refresh) : 0);
|
||||
if (ri->info)
|
||||
addReplyBulkCBuffer(c,ri->info,sdslen(ri->info));
|
||||
else
|
||||
@@ -3832,7 +3878,8 @@ NULL
|
||||
while ((sde = dictNext(sdi)) != NULL) {
|
||||
sentinelRedisInstance *sri = dictGetVal(sde);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c, now - sri->info_refresh);
|
||||
addReplyLongLong(c,
|
||||
ri->info_refresh ? (now - sri->info_refresh) : 0);
|
||||
if (sri->info)
|
||||
addReplyBulkCBuffer(c,sri->info,sdslen(sri->info));
|
||||
else
|
||||
@@ -3978,6 +4025,7 @@ void sentinelSetCommand(client *c) {
|
||||
int j, changes = 0;
|
||||
int badarg = 0; /* Bad argument position for error reporting. */
|
||||
char *option;
|
||||
int redacted;
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
@@ -3988,6 +4036,7 @@ void sentinelSetCommand(client *c) {
|
||||
option = c->argv[j]->ptr;
|
||||
long long ll;
|
||||
int old_j = j; /* Used to know what to log as an event. */
|
||||
redacted = 0;
|
||||
|
||||
if (!strcasecmp(option,"down-after-milliseconds") && moreargs > 0) {
|
||||
/* down-after-millisecodns <milliseconds> */
|
||||
@@ -4062,6 +4111,7 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
redacted = 1;
|
||||
} else if (!strcasecmp(option,"auth-user") && moreargs > 0) {
|
||||
/* auth-user <username> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
@@ -4108,16 +4158,16 @@ void sentinelSetCommand(client *c) {
|
||||
int numargs = j-old_j+1;
|
||||
switch(numargs) {
|
||||
case 2:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr);
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",(char*)c->argv[old_j]->ptr,
|
||||
redacted ? "******" : (char*)c->argv[old_j+1]->ptr);
|
||||
break;
|
||||
case 3:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr,
|
||||
c->argv[old_j+2]->ptr);
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",(char*)c->argv[old_j]->ptr,
|
||||
(char*)c->argv[old_j+1]->ptr,
|
||||
(char*)c->argv[old_j+2]->ptr);
|
||||
break;
|
||||
default:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",c->argv[old_j]->ptr);
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",(char*)c->argv[old_j]->ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -4689,6 +4739,7 @@ sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master) {
|
||||
max_master_down_time += master->down_after_period * 10;
|
||||
|
||||
di = dictGetIterator(master->slaves);
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *slave = dictGetVal(de);
|
||||
mstime_t info_validity_time;
|
||||
|
||||
+520
-201
File diff suppressed because it is too large
Load Diff
+100
-42
@@ -84,6 +84,12 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#include "endianconv.h"
|
||||
#include "crc64.h"
|
||||
|
||||
/* min/max */
|
||||
#undef min
|
||||
#undef max
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
/* Error codes */
|
||||
#define C_OK 0
|
||||
#define C_ERR -1
|
||||
@@ -99,6 +105,7 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#define PROTO_SHARED_SELECT_CMDS 10
|
||||
#define OBJ_SHARED_INTEGERS 10000
|
||||
#define OBJ_SHARED_BULKHDR_LEN 32
|
||||
#define OBJ_SHARED_HDR_STRLEN(_len_) (((_len_) < 10) ? 4 : 5) /* see shared.mbulkhdr etc. */
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
|
||||
#define AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
#define AOF_READ_DIFF_INTERVAL_BYTES (1024*10)
|
||||
@@ -130,6 +137,11 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
* special code. */
|
||||
#define SERVER_CHILD_NOERROR_RETVAL 255
|
||||
|
||||
/* Reading copy-on-write info is sometimes expensive and may slow down child
|
||||
* processes that report it continuously. We measure the cost of obtaining it
|
||||
* and hold back additional reading based on this factor. */
|
||||
#define CHILD_COW_DUTY_CYCLE 100
|
||||
|
||||
/* Instantaneous metrics tracking. */
|
||||
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
|
||||
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
|
||||
@@ -138,7 +150,6 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#define STATS_METRIC_COUNT 3
|
||||
|
||||
/* Protocol and I/O related defines */
|
||||
#define PROTO_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
|
||||
#define PROTO_IOBUF_LEN (1024*16) /* Generic I/O buffer size */
|
||||
#define PROTO_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */
|
||||
#define PROTO_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */
|
||||
@@ -275,6 +286,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
||||
and AOF client */
|
||||
#define CLIENT_REPL_RDBONLY (1ULL<<42) /* This client is a replica that only wants
|
||||
RDB without replication buffer. */
|
||||
#define CLIENT_PUSHING (1ULL<<43) /* This client is pushing notifications. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@@ -477,7 +489,8 @@ typedef enum {
|
||||
#define NOTIFY_STREAM (1<<10) /* t */
|
||||
#define NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from NOTIFY_ALL on purpose) */
|
||||
#define NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
|
||||
#define NOTIFY_MODULE (1<<13) /* d, module key space notification */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE) /* A flag */
|
||||
|
||||
/* Get the first bind addr or NULL */
|
||||
#define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)
|
||||
@@ -736,8 +749,6 @@ typedef struct multiState {
|
||||
int cmd_inv_flags; /* Same as cmd_flags, OR-ing the ~flags. so that it
|
||||
is possible to know if all the commands have a
|
||||
certain flag. */
|
||||
int minreplicas; /* MINREPLICAS for synchronous replication */
|
||||
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
|
||||
} multiState;
|
||||
|
||||
/* This structure holds the blocking operation state for a client.
|
||||
@@ -763,7 +774,6 @@ typedef struct blockingState {
|
||||
size_t xread_count; /* XREAD COUNT option. */
|
||||
robj *xread_group; /* XREADGROUP group name. */
|
||||
robj *xread_consumer; /* XREADGROUP consumer name. */
|
||||
mstime_t xread_retry_time, xread_retry_ttl;
|
||||
int xread_group_noack;
|
||||
|
||||
/* BLOCKED_WAIT */
|
||||
@@ -842,7 +852,7 @@ typedef struct {
|
||||
the flag ALLKEYS is set in the user. */
|
||||
list *channels; /* A list of allowed Pub/Sub channel patterns. If this
|
||||
field is NULL the user cannot mention any channel in a
|
||||
`PUBLISH` or [P][UNSUSBSCRIBE] command, unless the flag
|
||||
`PUBLISH` or [P][UNSUBSCRIBE] command, unless the flag
|
||||
ALLCHANNELS is set in the user. */
|
||||
} user;
|
||||
|
||||
@@ -880,6 +890,7 @@ typedef struct client {
|
||||
long bulklen; /* Length of bulk argument in multi bulk request. */
|
||||
list *reply; /* List of reply objects to send to the client. */
|
||||
unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */
|
||||
list *deferred_reply_errors; /* Used for module thread safe contexts. */
|
||||
size_t sentlen; /* Amount of bytes already sent in the current
|
||||
buffer or object being sent. */
|
||||
time_t ctime; /* Client creation time. */
|
||||
@@ -898,6 +909,7 @@ typedef struct client {
|
||||
long long reploff; /* Applied replication offset if this is a master. */
|
||||
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
|
||||
long long repl_ack_time;/* Replication ack time, if this is a slave. */
|
||||
long long repl_last_partial_write; /* The last time the server did a partial write from the RDB child pipe to this replica */
|
||||
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
|
||||
copying this slave output buffer
|
||||
should use. */
|
||||
@@ -982,11 +994,13 @@ struct sharedObjectsStruct {
|
||||
*script, *replconf, *eval, *persist, *set, *pexpireat, *pexpire,
|
||||
*time, *pxat, *px, *retrycount, *force, *justid,
|
||||
*lastid, *ping, *setid, *keepttl, *load, *createconsumer,
|
||||
*getack, *special_asterick, *special_equals, *default_username,
|
||||
*getack, *special_asterick, *special_equals, *default_username, *redacted,
|
||||
*select[PROTO_SHARED_SELECT_CMDS],
|
||||
*integers[OBJ_SHARED_INTEGERS],
|
||||
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
*bulkhdr[OBJ_SHARED_BULKHDR_LEN]; /* "$<value>\r\n" */
|
||||
*bulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "$<value>\r\n" */
|
||||
*maphdr[OBJ_SHARED_BULKHDR_LEN], /* "%<value>\r\n" */
|
||||
*sethdr[OBJ_SHARED_BULKHDR_LEN]; /* "~<value>\r\n" */
|
||||
sds minstring, maxstring;
|
||||
};
|
||||
|
||||
@@ -1105,6 +1119,11 @@ struct malloc_stats {
|
||||
size_t allocator_resident;
|
||||
};
|
||||
|
||||
typedef struct socketFds {
|
||||
int fd[CONFIG_BINDADDR_MAX];
|
||||
int count;
|
||||
} socketFds;
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* TLS Context Configuration
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -1112,8 +1131,10 @@ struct malloc_stats {
|
||||
typedef struct redisTLSContextConfig {
|
||||
char *cert_file; /* Server side and optionally client side cert file name */
|
||||
char *key_file; /* Private key filename for cert_file */
|
||||
char *key_file_pass; /* Optional password for key_file */
|
||||
char *client_cert_file; /* Certificate to use as a client; if none, use cert_file */
|
||||
char *client_key_file; /* Private key filename for client_cert_file */
|
||||
char *client_key_file_pass; /* Optional password for client_key_file */
|
||||
char *dh_params_file;
|
||||
char *ca_cert_file;
|
||||
char *ca_cert_dir;
|
||||
@@ -1196,6 +1217,7 @@ struct redisServer {
|
||||
to be processed. */
|
||||
pid_t child_pid; /* PID of current child */
|
||||
int child_type; /* Type of current child */
|
||||
client *module_client; /* "Fake" client to call Redis from modules */
|
||||
/* Networking */
|
||||
int port; /* TCP listening port */
|
||||
int tls_port; /* TLS listening port */
|
||||
@@ -1204,13 +1226,10 @@ struct redisServer {
|
||||
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
|
||||
char *unixsocket; /* UNIX socket path */
|
||||
mode_t unixsocketperm; /* UNIX socket permission */
|
||||
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
|
||||
int ipfd_count; /* Used slots in ipfd[] */
|
||||
int tlsfd[CONFIG_BINDADDR_MAX]; /* TLS socket file descriptors */
|
||||
int tlsfd_count; /* Used slots in tlsfd[] */
|
||||
socketFds ipfd; /* TCP socket file descriptors */
|
||||
socketFds tlsfd; /* TLS socket file descriptors */
|
||||
int sofd; /* Unix socket file descriptor */
|
||||
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
|
||||
int cfd_count; /* Used slots in cfd[] */
|
||||
socketFds cfd; /* Cluster bus listening socket */
|
||||
list *clients; /* List of active clients */
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
list *clients_pending_write; /* There is to write or install handler. */
|
||||
@@ -1219,6 +1238,7 @@ struct redisServer {
|
||||
client *current_client; /* Current client executing the command. */
|
||||
rax *clients_timeout_table; /* Radix tree for blocked clients timeouts. */
|
||||
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||
int in_nested_call; /* If > 0, in a nested call of a call */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
pause_type client_pause_type; /* True if clients are currently paused */
|
||||
list *paused_clients; /* List of pause clients */
|
||||
@@ -1279,6 +1299,7 @@ struct redisServer {
|
||||
redisAtomic long long stat_net_input_bytes; /* Bytes read from network. */
|
||||
redisAtomic long long stat_net_output_bytes; /* Bytes written to network. */
|
||||
size_t stat_current_cow_bytes; /* Copy on write bytes while child is active. */
|
||||
monotime stat_current_cow_updated; /* Last update time of stat_current_cow_bytes */
|
||||
size_t stat_current_save_keys_processed; /* Processed keys while child is active. */
|
||||
size_t stat_current_save_keys_total; /* Number of keys when child started. */
|
||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||
@@ -1325,6 +1346,7 @@ struct redisServer {
|
||||
int set_proc_title; /* True if change proc title */
|
||||
char *proc_title_template; /* Process title template format */
|
||||
clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT];
|
||||
int pause_cron; /* Don't run cron tasks (debug) */
|
||||
/* AOF persistence */
|
||||
int aof_enabled; /* AOF configuration */
|
||||
int aof_state; /* AOF_(ON|OFF|WAIT_REWRITE) */
|
||||
@@ -1351,9 +1373,11 @@ struct redisServer {
|
||||
int aof_rewrite_incremental_fsync;/* fsync incrementally while aof rewriting? */
|
||||
int rdb_save_incremental_fsync; /* fsync incrementally while rdb saving? */
|
||||
int aof_last_write_status; /* C_OK or C_ERR */
|
||||
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
|
||||
int aof_last_write_errno; /* Valid if aof write/fsync status is ERR */
|
||||
int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */
|
||||
int aof_use_rdb_preamble; /* Use RDB preamble on AOF rewrites. */
|
||||
redisAtomic int aof_bio_fsync_status; /* Status of AOF fsync in bio job. */
|
||||
redisAtomic int aof_bio_fsync_errno; /* Errno of AOF fsync in bio job. */
|
||||
/* AOF pipes used to communicate between parent and child during rewrite. */
|
||||
int aof_pipe_write_data_to_child;
|
||||
int aof_pipe_read_data_from_parent;
|
||||
@@ -1401,6 +1425,7 @@ struct redisServer {
|
||||
int child_info_nread; /* Num of bytes of the last read from pipe */
|
||||
/* Propagation of commands in AOF / replication */
|
||||
redisOpArray also_propagate; /* Additional command to propagate. */
|
||||
int replication_allowed; /* Are we allowed to replicate? */
|
||||
/* Logging */
|
||||
char *logfile; /* Path of log file */
|
||||
int syslog_enabled; /* Is syslog enabled? */
|
||||
@@ -1459,6 +1484,7 @@ struct redisServer {
|
||||
time_t repl_down_since; /* Unix time at which link with master went down */
|
||||
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
|
||||
int slave_priority; /* Reported in INFO and used by Sentinel. */
|
||||
int replica_announced; /* If true, replica is announced by Sentinel */
|
||||
int slave_announce_port; /* Give the master this listening port. */
|
||||
char *slave_announce_ip; /* Give the master this ip address. */
|
||||
/* The following two fields is where we store master PSYNC replid/offset
|
||||
@@ -1495,6 +1521,7 @@ struct redisServer {
|
||||
/* Client side caching. */
|
||||
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
|
||||
size_t tracking_table_max_keys; /* Max number of keys in tracking table. */
|
||||
list *tracking_pending_keys; /* tracking invalidation keys pending to flush */
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
@@ -1532,6 +1559,7 @@ struct redisServer {
|
||||
char *cluster_configfile; /* Cluster auto-generated config file name. */
|
||||
struct clusterState *cluster; /* State of the cluster */
|
||||
int cluster_migration_barrier; /* Cluster replicas migration barrier. */
|
||||
int cluster_allow_replica_migration; /* Automatic replica migrations to orphaned masters and from empty masters */
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
int cluster_require_full_coverage; /* If true, put the cluster down if
|
||||
there is at least an uncovered slot.*/
|
||||
@@ -1539,6 +1567,7 @@ struct redisServer {
|
||||
if the master is in failure state. */
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
int cluster_announce_tls_port; /* TLS port to announce on cluster bus. */
|
||||
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
|
||||
int cluster_module_flags; /* Set of flags that Redis modules are able
|
||||
to set in order to suppress certain
|
||||
@@ -1555,7 +1584,8 @@ struct redisServer {
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
mstime_t lua_time_start; /* Start time of script, milliseconds time */
|
||||
monotime lua_time_start; /* monotonic timer to detect timed-out script */
|
||||
mstime_t lua_time_snapshot; /* Snapshot of mstime when script is started */
|
||||
int lua_write_dirty; /* True if a write command was called during the
|
||||
execution of the current script. */
|
||||
int lua_random_dirty; /* True if a random command was called during the
|
||||
@@ -1583,7 +1613,7 @@ struct redisServer {
|
||||
sds requirepass; /* Remember the cleartext password set with
|
||||
the old "requirepass" directive for
|
||||
backward compatibility with Redis <= 5. */
|
||||
int acl_pubusub_default; /* Default ACL pub/sub channels flag */
|
||||
int acl_pubsub_default; /* Default ACL pub/sub channels flag */
|
||||
/* Assert & bug reporting */
|
||||
int watchdog_period; /* Software watchdog period in ms. 0 = off */
|
||||
/* System hardware info */
|
||||
@@ -1742,6 +1772,7 @@ void moduleLoadFromQueue(void);
|
||||
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
|
||||
moduleType *moduleTypeLookupModuleByID(uint64_t id);
|
||||
void moduleTypeNameByID(char *name, uint64_t moduleid);
|
||||
const char *moduleTypeModuleName(moduleType *mt);
|
||||
void moduleFreeContext(struct RedisModuleCtx *ctx);
|
||||
void unblockClientFromModule(client *c);
|
||||
void moduleHandleBlockedClients(void);
|
||||
@@ -1762,6 +1793,7 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data);
|
||||
void processModuleLoadingProgressEvent(int is_aof);
|
||||
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
||||
void moduleUnblockClient(client *c);
|
||||
int moduleBlockedClientMayTimeout(client *c);
|
||||
int moduleClientIsBlockedOnKeys(client *c);
|
||||
void moduleNotifyUserChanged(client *c);
|
||||
void moduleNotifyKeyUnlink(robj *key, robj *val);
|
||||
@@ -1777,7 +1809,7 @@ void getRandomHexChars(char *p, size_t len);
|
||||
void getRandomBytes(unsigned char *p, size_t len);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
void exitFromChild(int retcode);
|
||||
size_t redisPopcount(void *s, long count);
|
||||
long long redisPopcount(void *s, long count);
|
||||
int redisSetProcTitle(char *title);
|
||||
int validateProcTitleTemplate(const char *template);
|
||||
int redisCommunicateSystemd(const char *sd_notify_msg);
|
||||
@@ -1823,6 +1855,7 @@ void addReplyErrorSds(client *c, sds err);
|
||||
void addReplyError(client *c, const char *err);
|
||||
void addReplyStatus(client *c, const char *status);
|
||||
void addReplyDouble(client *c, double d);
|
||||
void addReplyBigNum(client *c, const char* num, size_t len);
|
||||
void addReplyHumanLongDouble(client *c, long double d);
|
||||
void addReplyLongLong(client *c, long long ll);
|
||||
void addReplyArrayLen(client *c, long length);
|
||||
@@ -1834,6 +1867,7 @@ void addReplyHelp(client *c, const char **help);
|
||||
void addReplySubcommandSyntaxError(client *c);
|
||||
void addReplyLoadedModules(client *c);
|
||||
void copyClientOutputBuffer(client *dst, client *src);
|
||||
void deferredAfterErrorReply(client *c, list *errors);
|
||||
size_t sdsZmallocSize(sds s);
|
||||
size_t getStringObjectSdsUsedMemory(robj *o);
|
||||
void freeClientReplyValue(void *o);
|
||||
@@ -1847,15 +1881,16 @@ sds getAllClientsInfoString(int type);
|
||||
void rewriteClientCommandVector(client *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(client *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv);
|
||||
void redactClientCommandArgument(client *c, int argc);
|
||||
unsigned long getClientOutputBufferMemoryUsage(client *c);
|
||||
int freeClientsInAsyncFreeQueue(void);
|
||||
void asyncCloseClientOnOutputBufferLimitReached(client *c);
|
||||
int closeClientOnOutputBufferLimitReached(client *c, int async);
|
||||
int getClientType(client *c);
|
||||
int getClientTypeByName(char *name);
|
||||
char *getClientTypeName(int class);
|
||||
void flushSlavesOutputBuffers(void);
|
||||
void disconnectSlaves(void);
|
||||
int listenToPort(int port, int *fds, int *count);
|
||||
int listenToPort(int port, socketFds *fds);
|
||||
void pauseClients(mstime_t duration, pause_type type);
|
||||
void unpauseClients(void);
|
||||
int areClientsPaused(void);
|
||||
@@ -1877,6 +1912,7 @@ void protectClient(client *c);
|
||||
void unprotectClient(client *c);
|
||||
void initThreadedIO(void);
|
||||
client *lookupClientByID(uint64_t id);
|
||||
int authRequired(client *c);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(client *c, const char *fmt, ...)
|
||||
@@ -1892,8 +1928,11 @@ void addReplyStatusFormat(client *c, const char *fmt, ...);
|
||||
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix);
|
||||
void disableTracking(client *c);
|
||||
void trackingRememberKeys(client *c);
|
||||
void trackingInvalidateKey(client *c, robj *keyobj);
|
||||
void trackingInvalidateKey(client *c, robj *keyobj, int bcast);
|
||||
void trackingScheduleKeyInvalidation(uint64_t client_id, robj *keyobj);
|
||||
void trackingHandlePendingKeyInvalidations(void);
|
||||
void trackingInvalidateKeysOnFlush(int async);
|
||||
void freeTrackingRadixTree(rax *rt);
|
||||
void freeTrackingRadixTreeAsync(rax *rt);
|
||||
void trackingLimitUsedSlots(void);
|
||||
uint64_t trackingGetTotalItems(void);
|
||||
@@ -1926,13 +1965,15 @@ void initClientMultiState(client *c);
|
||||
void freeClientMultiState(client *c);
|
||||
void queueMultiCommand(client *c);
|
||||
void touchWatchedKey(redisDb *db, robj *key);
|
||||
int isWatchedKeyExpired(client *c);
|
||||
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with);
|
||||
void discardTransaction(client *c);
|
||||
void flagTransaction(client *c);
|
||||
void execCommandAbort(client *c, sds error);
|
||||
void execCommandPropagateMulti(int dbid);
|
||||
void execCommandPropagateExec(int dbid);
|
||||
void beforePropagateMultiOrExec(int multi);
|
||||
void beforePropagateMulti();
|
||||
void afterPropagateExec();
|
||||
|
||||
/* Redis object implementation */
|
||||
void decrRefCount(robj *o);
|
||||
@@ -1949,6 +1990,8 @@ robj *createObject(int type, void *ptr);
|
||||
robj *createStringObject(const char *ptr, size_t len);
|
||||
robj *createRawStringObject(const char *ptr, size_t len);
|
||||
robj *createEmbeddedStringObject(const char *ptr, size_t len);
|
||||
robj *tryCreateRawStringObject(const char *ptr, size_t len);
|
||||
robj *tryCreateStringObject(const char *ptr, size_t len);
|
||||
robj *dupStringObject(const robj *o);
|
||||
int isSdsRepresentableAsLongLong(sds s, long long *llval);
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
|
||||
@@ -2081,7 +2124,7 @@ int isMutuallyExclusiveChildType(int type);
|
||||
extern rax *Users;
|
||||
extern user *DefaultUser;
|
||||
void ACLInit(void);
|
||||
/* Return values for ACLCheckCommandPerm() and ACLCheckPubsubPerm(). */
|
||||
/* Return values for ACLCheckAllPerm(). */
|
||||
#define ACL_OK 0
|
||||
#define ACL_DENIED_CMD 1
|
||||
#define ACL_DENIED_KEY 2
|
||||
@@ -2092,8 +2135,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password);
|
||||
unsigned long ACLGetCommandID(const char *cmdname);
|
||||
void ACLClearCommandID(void);
|
||||
user *ACLGetUserByName(const char *name, size_t namelen);
|
||||
int ACLCheckCommandPerm(client *c, int *keyidxptr);
|
||||
int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr);
|
||||
int ACLCheckAllPerm(client *c, int *idxptr);
|
||||
int ACLSetUser(user *u, const char *op, ssize_t oplen);
|
||||
sds ACLDefaultUserFirstPassword(void);
|
||||
uint64_t ACLGetCommandCategoryFlagByName(const char *name);
|
||||
@@ -2106,25 +2148,23 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd);
|
||||
user *ACLCreateUnlinkedUser();
|
||||
void ACLFreeUserAndKillClients(user *u);
|
||||
void addACLLogEntry(client *c, int reason, int keypos, sds username);
|
||||
void ACLUpdateDefaultUserPassword(sds password);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
/* Input flags. */
|
||||
#define ZADD_NONE 0
|
||||
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */
|
||||
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */
|
||||
#define ZADD_XX (1<<2) /* Only touch elements already existing. */
|
||||
#define ZADD_GT (1<<7) /* Only update existing when new scores are higher. */
|
||||
#define ZADD_LT (1<<8) /* Only update existing when new scores are lower. */
|
||||
#define ZADD_IN_NONE 0
|
||||
#define ZADD_IN_INCR (1<<0) /* Increment the score instead of setting it. */
|
||||
#define ZADD_IN_NX (1<<1) /* Don't touch elements not already existing. */
|
||||
#define ZADD_IN_XX (1<<2) /* Only touch elements already existing. */
|
||||
#define ZADD_IN_GT (1<<3) /* Only update existing when new scores are higher. */
|
||||
#define ZADD_IN_LT (1<<4) /* Only update existing when new scores are lower. */
|
||||
|
||||
/* Output flags. */
|
||||
#define ZADD_NOP (1<<3) /* Operation not performed because of conditionals.*/
|
||||
#define ZADD_NAN (1<<4) /* Only touch elements already existing. */
|
||||
#define ZADD_ADDED (1<<5) /* The element was new and was added. */
|
||||
#define ZADD_UPDATED (1<<6) /* The element already existed, score updated. */
|
||||
|
||||
/* Flags only used by the ZADD command but not by zsetAdd() API: */
|
||||
#define ZADD_CH (1<<16) /* Return num of elements added or updated. */
|
||||
#define ZADD_OUT_NOP (1<<0) /* Operation not performed because of conditionals.*/
|
||||
#define ZADD_OUT_NAN (1<<1) /* Only touch elements already existing. */
|
||||
#define ZADD_OUT_ADDED (1<<2) /* The element was new and was added. */
|
||||
#define ZADD_OUT_UPDATED (1<<3) /* The element already existed, score updated. */
|
||||
|
||||
/* Struct to hold an inclusive/exclusive range spec by score comparison. */
|
||||
typedef struct {
|
||||
@@ -2152,10 +2192,10 @@ unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
|
||||
unsigned char *zzlLastInRange(unsigned char *zl, zrangespec *range);
|
||||
unsigned long zsetLength(const robj *zobj);
|
||||
void zsetConvert(robj *zobj, int encoding);
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen);
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen);
|
||||
int zsetScore(robj *zobj, sds member, double *score);
|
||||
unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
|
||||
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore);
|
||||
int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore);
|
||||
long zsetRank(robj *zobj, sds ele, int reverse);
|
||||
int zsetDel(robj *zobj, sds ele);
|
||||
robj *zsetDup(robj *o);
|
||||
@@ -2183,6 +2223,9 @@ int processCommand(client *c);
|
||||
int processPendingCommandsAndResetClient(client *c);
|
||||
void setupSignalHandlers(void);
|
||||
void removeSignalHandlers(void);
|
||||
int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler);
|
||||
int changeListenPort(int port, socketFds *sfd, aeFileProc *accept_handler);
|
||||
int changeBindAddr(sds *addrlist, int addrlist_len);
|
||||
struct redisCommand *lookupCommand(sds name);
|
||||
struct redisCommand *lookupCommandByCString(const char *s);
|
||||
struct redisCommand *lookupCommandOrOriginal(sds name);
|
||||
@@ -2195,12 +2238,15 @@ void forceCommandPropagation(client *c, int flags);
|
||||
void preventCommandPropagation(client *c);
|
||||
void preventCommandAOF(client *c);
|
||||
void preventCommandReplication(client *c);
|
||||
void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration);
|
||||
int prepareForShutdown(int flags);
|
||||
void afterCommand(client *c);
|
||||
int inNestedCall(void);
|
||||
#ifdef __GNUC__
|
||||
void serverLog(int level, const char *fmt, ...)
|
||||
void _serverLog(int level, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
#else
|
||||
void serverLog(int level, const char *fmt, ...);
|
||||
void _serverLog(int level, const char *fmt, ...);
|
||||
#endif
|
||||
void serverLogRaw(int level, const char *msg);
|
||||
void serverLogFromHandler(int level, const char *msg);
|
||||
@@ -2296,7 +2342,9 @@ void initConfigValues();
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
int removeExpire(redisDb *db, robj *key);
|
||||
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj);
|
||||
void propagateExpire(redisDb *db, robj *key, int lazy);
|
||||
int keyIsExpired(redisDb *db, robj *key);
|
||||
int expireIfNeeded(redisDb *db, robj *key);
|
||||
long long getExpire(redisDb *db, robj *key);
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when);
|
||||
@@ -2310,6 +2358,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *objectCommandLookup(client *c, robj *key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
void SentReplyOnKeyMiss(client *c, robj *reply);
|
||||
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock, int lru_multiplier);
|
||||
#define LOOKUP_NONE 0
|
||||
@@ -2389,6 +2438,7 @@ const char *sentinelHandleConfiguration(char **argv, int argc);
|
||||
void queueSentinelConfig(sds *argv, int argc, int linenum, sds line);
|
||||
void loadSentinelConfigFromQueue(void);
|
||||
void sentinelIsRunning(void);
|
||||
void sentinelCheckConfigFile(void);
|
||||
|
||||
/* redis-check-rdb & aof */
|
||||
int redis_check_rdb(char *rdbfilename, FILE *fp);
|
||||
@@ -2706,8 +2756,16 @@ void killIOThreads(void);
|
||||
void killThreads(void);
|
||||
void makeThreadKillable(void);
|
||||
|
||||
/* Use macro for checking log level to avoid evaluating arguments in cases log
|
||||
* should be ignored due to low level. */
|
||||
#define serverLog(level, ...) do {\
|
||||
if (((level)&0xff) < server.verbosity) break;\
|
||||
_serverLog(level, __VA_ARGS__);\
|
||||
} while(0)
|
||||
|
||||
/* TLS stuff */
|
||||
void tlsInit(void);
|
||||
void tlsCleanup(void);
|
||||
int tlsConfigure(redisTLSContextConfig *ctx_config);
|
||||
|
||||
#define redisDebug(fmt, ...) \
|
||||
|
||||
+1
-1
@@ -232,7 +232,7 @@ void spt_init(int argc, char *argv[]) {
|
||||
if (!(SPT.arg0 = strdup(argv[0])))
|
||||
goto syerr;
|
||||
|
||||
#if __GLIBC__
|
||||
#if __linux__
|
||||
if (!(tmp = strdup(program_invocation_name)))
|
||||
goto syerr;
|
||||
|
||||
|
||||
+2
-1
@@ -201,7 +201,7 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
|
||||
#define BUFSIZE 4096
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
int sha1Test(int argc, char **argv)
|
||||
int sha1Test(int argc, char **argv, int accurate)
|
||||
{
|
||||
SHA1_CTX ctx;
|
||||
unsigned char hash[20], buf[BUFSIZE];
|
||||
@@ -209,6 +209,7 @@ int sha1Test(int argc, char **argv)
|
||||
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(accurate);
|
||||
|
||||
for(i=0;i<BUFSIZE;i++)
|
||||
buf[i] = i;
|
||||
|
||||
+1
-1
@@ -19,6 +19,6 @@ void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
|
||||
void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int sha1Test(int argc, char **argv);
|
||||
int sha1Test(int argc, char **argv, int accurate);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user