Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
92bde124ca | ||
|
|
7018ad69a2 | ||
|
|
46df4db87b | ||
|
|
445aa844b9 | ||
|
|
f098fe319b | ||
|
|
2ab6fef099 | ||
|
|
2dba1e391d | ||
|
|
95338f9cc4 | ||
|
|
d0854927fc | ||
|
|
ec2d180739 | ||
|
|
049f2f0805 | ||
|
|
b34fc03e5e | ||
|
|
b8c67ce41b |
@@ -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
|
||||
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
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
|
||||
@@ -171,7 +171,7 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -247,20 +247,21 @@ jobs:
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-freebsd:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-10.15
|
||||
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.1.6
|
||||
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
|
||||
|
||||
+744
-11
@@ -1,16 +1,749 @@
|
||||
Hello! This file is just a placeholder, since this is the "unstable" branch
|
||||
of Redis, the place where all the development happens.
|
||||
Redis 6.2 release notes
|
||||
=======================
|
||||
|
||||
There is no release notes for this branch, it gets forked into another branch
|
||||
every time there is a partial feature freeze in order to eventually create
|
||||
a new stable release.
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
|
||||
Usually "unstable" is stable enough for you to use it in development environments
|
||||
however you should never use it in production environments. It is possible
|
||||
to download the latest stable release here:
|
||||
LOW: No need to upgrade unless there are new features you want to use.
|
||||
MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
http://download.redis.io/releases/redis-stable.tar.gz
|
||||
================================================================================
|
||||
Redis 6.2.7 Released Wed Apr 27 12:00:00 IDT 2022
|
||||
================================================================================
|
||||
|
||||
More information is available at https://redis.io
|
||||
Upgrade urgency: SECURITY, contains fixes to security issues.
|
||||
|
||||
Happy hacking!
|
||||
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
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY if you use 32bit build of redis (see bellow), MODERATE
|
||||
if you used earlier versions of Redis 6.2, LOW otherwise.
|
||||
|
||||
Integer overflow on 32-bit systems (CVE-2021-21309):
|
||||
Redis 4.0 or newer uses a configurable limit for the maximum supported bulk
|
||||
input size. By default, it is 512MB which is a safe value for all platforms.
|
||||
If the limit is significantly increased, receiving a large request from a client
|
||||
may trigger several integer overflow scenarios, which would result with buffer
|
||||
overflow and heap corruption.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2 RC3,
|
||||
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:
|
||||
* Avoid 32-bit overflows when proto-max-bulk-len is set high (#8522)
|
||||
* Fix broken protocol in client tracking tracking-redir-broken message (#8456)
|
||||
* Avoid unsafe field name characters in INFO commandstats, errorstats, modules (#8492)
|
||||
* XINFO able to access expired keys during CLIENT PAUSE WRITE (#8436)
|
||||
* Fix allowed length for REPLCONF ip-address, needed due to Sentinel's support for hostnames (#8517)
|
||||
* Fix broken protocol in redis-benchmark when used with -a or --dbnum (#8486)
|
||||
* XADD counts deleted records too when considering switching to a new listpack (#8390)
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Fixes in GEOSEARCH bybox (accuracy and mismatch between width and height) (#8445)
|
||||
* Fix risk of OOM panic in HRANDFIELD, ZRANDMEMBER commands with huge negative count (#8429)
|
||||
* Fix duplicate replicas issue in Sentinel, needed due to hostname support (#8481)
|
||||
* Fix Sentinel configuration rewrite, an improvement of #8271 (#8480)
|
||||
|
||||
Command behavior changes:
|
||||
* SRANDMEMBER uses RESP3 array type instead of set type (#8504)
|
||||
* EXPIRE, EXPIREAT, SETEX, GETEX: Return error when provided expire time overflows (#8287)
|
||||
|
||||
Other behavior changes:
|
||||
* Remove ACL subcommand validation if fully added command exists. (#8483)
|
||||
|
||||
Improvements:
|
||||
* Optimize sorting in GEORADIUS / GEOSEARCH with COUNT (#8326)
|
||||
* Optimize HRANDFIELD and ZRANDMEMBER case 4 when ziplist encoded (#8444)
|
||||
* Optimize in-place replacement of elements in HSET, HINCRBY, LSET (#8493)
|
||||
* Remove redundant list to store pubsub patterns (#8472)
|
||||
* Add --insecure option to command line tools (#8416)
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Add INFO fields to track progress of BGSAVE, AOFRW, replication (#8414)
|
||||
|
||||
Modules:
|
||||
* RM_ZsetRem: Delete key if empty, the bug could leave empty zset keys (#8453)
|
||||
* RM_HashSet: Add COUNT_ALL flag and set errno (#8446)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2 RC3 Released Tue Feb 1 14:00:00 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the third Release Candidate of Redis 6.2.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2 RC2,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
New commands / args:
|
||||
* Add HRANDFIELD and ZRANDMEMBER commands (#8297)
|
||||
* Add FAILOVER command (#8315)
|
||||
* Add GETEX, GETDEL commands (#8327)
|
||||
* Add PXAT/EXAT arguments to SET command (#8327)
|
||||
* Add SYNC arg to FLUSHALL and FLUSHDB, and ASYNC/SYNC arg to SCRIPT FLUSH (#8258)
|
||||
|
||||
Sentinel:
|
||||
* Add hostname support to Sentinel (#8282)
|
||||
* Prevent file descriptors from leaking into Sentinel scripts (#8242)
|
||||
* Fix config file line order dependency and config rewrite sequence (#8271)
|
||||
|
||||
New configuration options:
|
||||
* Add set-proc-title config option to disable changes to the process title (#3623)
|
||||
* Add proc-title-template option to control what's shown in the process title (#8397)
|
||||
* Add lazyfree-lazy-user-flush config option to control FLUSHALL, FLUSHDB and SCRIPT FLUSH (#8258)
|
||||
|
||||
Bug fixes:
|
||||
* AOF: recover from last write error by turning on/off appendonly config (#8030)
|
||||
* Exit on fsync error when the AOF fsync policy is 'always' (#8347)
|
||||
* Avoid assertions (on older kernels) when testing arm64 CoW bug (#8405)
|
||||
* CONFIG REWRITE should honor umask settings (#8371)
|
||||
* Fix firstkey,lastkey,step in COMMAND command for some commands (#8367)
|
||||
|
||||
Special considerations:
|
||||
* Fix misleading description of the save configuration directive (#8337)
|
||||
|
||||
Improvements:
|
||||
* A way to get RDB file via replication without excessive replication buffers (#8303)
|
||||
* Optimize performance of clusterGenNodesDescription for large clusters (#8182)
|
||||
|
||||
Info fields and introspection changes:
|
||||
* SLOWLOG and LATENCY monitor include unblocking time of blocked commands (#7491)
|
||||
|
||||
Modules:
|
||||
* Add modules API for streams (#8288)
|
||||
* Add event for fork child birth and termination (#8289)
|
||||
* Add RM_BlockedClientMeasureTime* etc, to track background processing in commandstats (#7491)
|
||||
* Fix bug in v6.2, wrong value passed to the new unlink callback (#8381)
|
||||
* Fix bug in v6.2, modules blocked on keys unblock on commands like LPUSH (#8356)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2 RC2 Released Tue Jan 12 16:17:20 IST 2021
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the second Release Candidate of Redis 6.2.
|
||||
|
||||
IMPORTANT: If you're running Redis on ARM64 or a big-endian system, upgrade may
|
||||
have significant implications. Please be sure to read the notes below.
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.2 RC1,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
New commands / args:
|
||||
* Add the REV, BYLEX and BYSCORE arguments to ZRANGE, and the ZRANGESTORE command (#7844)
|
||||
* Add the XAUTOCLAIM command (#7973)
|
||||
* Add the MINID trimming strategy and the LIMIT argument to XADD and XTRIM (#8169)
|
||||
* Add the ANY argument to GEOSEARCH and GEORADIUS (#8259)
|
||||
* Add the CH, NX, XX arguments to GEOADD (#8227)
|
||||
* Add the COUNT argument to LPOP and RPOP (#8179)
|
||||
* Add the WRITE argument to CLIENT PAUSE for pausing write commands exclusively (#8170)
|
||||
* Change the proto-ver argument of HELLO to optional (#7377)
|
||||
* Add the CLIENT TRACKINGINFO subcommand (#7309)
|
||||
|
||||
Command behavior changes:
|
||||
* CLIENT TRACKING yields an error when given overlapping BCAST prefixes (#8176)
|
||||
* SWAPDB invalidates WATCHed keys (#8239)
|
||||
* SORT command behaves differently when used on a writable replica (#8283)
|
||||
|
||||
Other behavior changes:
|
||||
* Avoid propagating MULTI/EXEC for read-only transactions (#8216)
|
||||
* Remove the read-only flag from TIME, ECHO, ROLE, LASTSAVE (#8216)
|
||||
* Fix the command flags of PFDEBUG (#8222)
|
||||
* Tracking clients will no longer receive unnecessary key invalidation messages after FLUSHDB (#8039)
|
||||
* Sentinel: Fix missing updates to the config file after SENTINEL SET command (#8229)
|
||||
|
||||
Bug fixes with compatibility implications (bugs introduced in Redis 6.0):
|
||||
* Fix RDB CRC64 checksum on big-endian systems (#8270)
|
||||
If you're using big-endian please consider the compatibility implications with
|
||||
RESTORE, replication and persistence.
|
||||
* Fix wrong order of key/value in Lua's map response (#8266)
|
||||
If your scripts use redis.setresp() or return a map (new in Redis 6.0), please
|
||||
consider the implications.
|
||||
|
||||
Bug fixes that are only applicable to previous releases of Redis 6.2:
|
||||
* Resolve rare assertions in active defragmentation while loading (#8284, #8281)
|
||||
|
||||
Bug fixes:
|
||||
* Fix the selection of a random element from large hash tables (#8133)
|
||||
* Fix an issue where a forked process deletes the parent's pidfile (#8231)
|
||||
* Fix crashes when enabling io-threads-do-reads (#8230)
|
||||
* Fix a crash in redis-cli after executing cluster backup (#8267)
|
||||
* Fix redis-benchmark to use an IP address for the first cluster node (#8154)
|
||||
* Fix saving of strings larger than 2GB into RDB files (#8306)
|
||||
|
||||
Additional improvements:
|
||||
* Improve replication handshake time (#8214)
|
||||
* Release client tracking table memory asynchronously in cases where the DB is also freed asynchronously (#8039)
|
||||
* Avoid wasteful transient memory allocation in certain cases (#8286, #5954)
|
||||
* Handle binary string values by the 'requirepass' and 'masterauth' configs (#8200)
|
||||
|
||||
Platform and deployment-related changes:
|
||||
* Install redis-check-rdb and redis-check-aof as symlinks to redis-server (#5745)
|
||||
* Add a check for an ARM64 Linux kernel bug (#8224)
|
||||
Due to the potential severity of this issue, Redis will refuse to run on
|
||||
affected platforms by default.
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Add the errorstats section to the INFO command (#8217)
|
||||
* Add the failed_calls and rejected_calls fields INFO's commandstats section (#8217)
|
||||
* Report child copy-on-write metrics continuously (#8264)
|
||||
|
||||
Module API changes:
|
||||
* Add the RedisModule_SendChildCOWInfo API (#8264)
|
||||
* Add the may-replicate command flag (#8170)
|
||||
|
||||
================================================================================
|
||||
Redis 6.2 RC1 Released Mon Dec 14 11:50:00 IST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency LOW: This is the first Release Candidate of Redis 6.2.
|
||||
|
||||
Introduction to the Redis 6.2 release
|
||||
=====================================
|
||||
|
||||
This release is the first significant Redis release managed by the core team
|
||||
under the new project governance model.
|
||||
|
||||
Redis 6.2 includes many new commands and improvements, but no big features. It
|
||||
mainly makes Redis more complete and addresses issues that have been requested
|
||||
by many users frequently or for a long time.
|
||||
|
||||
Many of these changes were not eligible for 6.0.x for several reasons:
|
||||
|
||||
1. They are not backward compatible, which is always the case with new or
|
||||
extended commands (that cannot be replicated to an older replica).
|
||||
2. They require a longer release-candidate test cycle.
|
||||
|
||||
|
||||
Here is a comprehensive list of changes in this release compared to 6.0.9,
|
||||
each one includes the PR number that added it, so you can get more details
|
||||
at https://github.com/redis/redis/pull/<number>
|
||||
|
||||
New commands / args:
|
||||
* Add SMISMEMBER command that checks multiple members (#7615)
|
||||
* Add ZMSCORE command that returns an array of scores (#7593)
|
||||
* Add LMOVE and BLMOVE commands that pop and push arbitrarily (#6929)
|
||||
* Add RESET command that resets client connection state (#7982)
|
||||
* Add COPY command that copies keys (#7953)
|
||||
* Add ZDIFF and ZDIFFSTORE commands (#7961)
|
||||
* Add ZINTER and ZUNION commands (#7794)
|
||||
* Add GEOSEARCH/GEOSEARCHSTORE commands for bounding box spatial queries (#8094)
|
||||
* Add GET parameter to SET command, for more powerful GETSET (#7852)
|
||||
* Add exclusive range query to XPENDING (#8130)
|
||||
* Add exclusive range query to X[REV]RANGE (#8072)
|
||||
* Add GT and LT options to ZADD for conditional score updates (#7818)
|
||||
* Add CLIENT INFO and CLIENT LIST for specific ids (#8113)
|
||||
* Add IDLE argument to XPENDING command (#7972)
|
||||
* Add local address to CLIENT LIST, and a CLIENT KILL filter. (#7913)
|
||||
* Add NOMKSTREAM option to XADD command (#7910)
|
||||
* Add command introspection to Sentinel (#7940)
|
||||
* Add SENTINEL MYID subcommand (#7858)
|
||||
|
||||
New features:
|
||||
* Dump payload sanitization: prevent corrupt payload causing crashes (#7807)
|
||||
Has flags to enable full O(N) validation (disabled by default).
|
||||
* ACL patterns for Pub/Sub channels (#7993)
|
||||
* Support ACL for Sentinel mode (#7888)
|
||||
* Support getting configuration from both stdin and file at the same time (#7893)
|
||||
Lets you avoid storing secrets on the disk.
|
||||
|
||||
New features in CLI tools:
|
||||
* redis-cli RESP3 push support (#7609)
|
||||
* redis-cli cluster import support source and target that require auth (#7994)
|
||||
* redis-cli URIs able to provide user name in addition to password (#8048)
|
||||
* redis-cli/redis-benchmark allow specifying the prefered ciphers/ciphersuites (#8005)
|
||||
* redis-cli add -e option to exit with code when command execution fails (#8136)
|
||||
|
||||
Command behavior changes:
|
||||
* EXISTS should not alter LRU (#8016)
|
||||
In Redis 5.0 and 6.0 it would have touched the LRU/LFU of the key.
|
||||
* OBJECT should not reveal logically expired keys (#8016)
|
||||
Will now behave the same TYPE or any other non-DEBUG command.
|
||||
* Improve db id range check for SELECT and MOVE (#8085)
|
||||
Changes the error message text on a wrong db index.
|
||||
* Modify AUTH / HELLO error message (#7648)
|
||||
Changes the error message text when the user isn't found or is disabled.
|
||||
* BITOPS length limited to proto_max_bulk_len rather than 512MB (#8096)
|
||||
The limit is now configurable like in SETRANGE, and APPEND.
|
||||
* GEORADIUS[BYMEMBER] can fail with -OOM if Redis is over the memory limit (#8107)
|
||||
|
||||
Other behavior changes:
|
||||
* Optionally (default) fail to start if requested bind address is not available (#7936)
|
||||
If you rely on Redis starting successfully even if one of the bind addresses
|
||||
is not available, you'll need to tune the new config.
|
||||
* Limit the main db dictionaries expansion to prevent key eviction (#7954)
|
||||
In the past big dictionary rehashing could result in massive data eviction.
|
||||
Now this rehashing is delayed (up to a limit), which can result in performance
|
||||
loss due to hash collisions.
|
||||
* CONFIG REWRITE is atomic and safer, but requires write access to the config file's folder (#7824, #8051)
|
||||
This change was already present in 6.0.9, but was missing from the release
|
||||
notes.
|
||||
* A new incremental eviction mechanism that reduces latency on eviction spikes (#7653)
|
||||
In pathological cases this can cause memory to grow uncontrolled and may require
|
||||
specific tuning.
|
||||
* Not resetting "save" config when Redis is started with command line arguments. (#7092)
|
||||
In case you provide command line arguments without "save" and count on it
|
||||
being disabled, Now the defaults "save" config will kick in.
|
||||
* Update memory metrics for INFO during loading (#7690)
|
||||
* When "supervised" config is enabled, it takes precedence over "daemonize". (#8036)
|
||||
* Assertion and panic, print crash log without generating SIGSEGV (#7585)
|
||||
* Added crash log report on SIGABRT, instead of silently exiting (#8004)
|
||||
* Disable THP (Transparent Huge Pages) if enabled (#7381)
|
||||
If you deliberately enabled it, you'll need to config Redis to keep it.
|
||||
|
||||
Bug fixes:
|
||||
* Handle output buffer limits for module blocked clients (#8141)
|
||||
Could result in a module sending reply to a blocked client to go beyond the
|
||||
limit.
|
||||
* Fix setproctitle related crashes. (#8150, #8088)
|
||||
Caused various crashes on startup, mainly on Apple M1 chips or under
|
||||
instrumentation.
|
||||
* A module doing RM_Call could cause replicas to get nested MULTI (#8097).
|
||||
* Backup/restore cluster mode keys to slots map for repl-diskless-load=swapdb (#8108)
|
||||
In cluster mode with repl-diskless-load, when loading failed, slot map
|
||||
wouldn't have been restored.
|
||||
* Fix oom-score-adj-values range, and bug when used in config file (#8046)
|
||||
Enabling setting this in the config file in a line after enabling it, would
|
||||
have been buggy.
|
||||
* Reset average ttl when empty databases (#8106)
|
||||
Just causing misleading metric in INFO
|
||||
* Disable rehash when Redis has child process (#8007)
|
||||
This could have caused excessive CoW during BGSAVE, replication or AOFRW.
|
||||
* Further improved ACL algorithm for picking categories (#7966)
|
||||
Output of ACL GETUSER is now more similar to the one provided by ACL SETUSER.
|
||||
* Fix bug with module GIL being released prematurely (#8061)
|
||||
Could in theory (and rarely) cause multi-threaded modules to corrupt memory.
|
||||
* Fix cluster redirect for module command with no firstkey. (#7539)
|
||||
* Reduce effect of client tracking causing feedback loop in key eviction (#8100)
|
||||
* Kill disk-based fork child when all replicas drop and 'save' is not enabled (#7819)
|
||||
* Rewritten commands (modified for propagation) are logged as their original command (#8006)
|
||||
* Fix cluster access to unaligned memory (SIGBUS on old ARM) #7958
|
||||
* If diskless repl child is killed, make sure to reap the child pid (#7742)
|
||||
* Broadcast a PONG message when slot's migration is over, may reduce MOVED responses (#7571)
|
||||
|
||||
Other improvements:
|
||||
* TLS Support in redis-benchmark (#7959)
|
||||
* Accelerate diskless master connections, and general re-connections (#6271)
|
||||
* Run active defrag while blocked / loading (#7726)
|
||||
* Performance and memory reporting improvement - sds take control of its internal fragmentation (#7875)
|
||||
* Speedup cluster failover. (#7948)
|
||||
|
||||
Platform / toolchain support related improvements:
|
||||
* Optionally (not by default) use H/W Monotonic clock for faster time sampling (#7644)
|
||||
* Remove the requirements for C11 and _Atomic supporting compiler (#7707)
|
||||
This would allow to more easily build and use Redis on older systems and
|
||||
compilers again.
|
||||
* Fix crash log registers output on ARM. (#8020)
|
||||
* Raspberry build fix. (#8095)
|
||||
* Setting process title support for Haiku. (#8060)
|
||||
* DragonFlyBSD RSS memory sampling support. (#8023)
|
||||
|
||||
New configuration options:
|
||||
* Enable configuring OpenSSL using the standard openssl.cnf (#8143)
|
||||
* oom-score-adj-values config can now take absolute values (besides relative ones) (#8046)
|
||||
* TLS: Add different client cert support. (#8076)
|
||||
* Note that a few other changes listed above added their config options.
|
||||
|
||||
Info fields and introspection changes:
|
||||
* Add INFO fields to track diskless and disk-based replication progress (#7981)
|
||||
* Add INFO field for main thread cpu time, and scrape system time. (#8132)
|
||||
* Add total_forks to INFO STATS (#8155)
|
||||
* Add maxclients and cluster_connections to INFO CLIENTS (#7979)
|
||||
* Add tracking bcast flag and client redirection in client list (#7995)
|
||||
* Fixed INFO client_recent_max_input_buffer includes argv array (#8065, see #7874)
|
||||
* Note that a few other changes listed above added their info fields.
|
||||
|
||||
Module API changes:
|
||||
* Add CTX_FLAGS_DENY_BLOCKING as a unified the way to know if blocking is allowed (#8025)
|
||||
* Add data type callbacks for lazy free effort, and unlink (#7912)
|
||||
* Add data type callback for COPY command (#8112)
|
||||
* Add callbacks for defrag support. (#8149)
|
||||
* Add module event for repl-diskless-load swapdb (#8153)
|
||||
|
||||
Module related fixes:
|
||||
* Moved RMAPI_FUNC_SUPPORTED so that it's usable (#8037)
|
||||
* Improve timer accuracy (#7987)
|
||||
* Allow '\0' inside of result of RM_CreateStringPrintf (#6260)
|
||||
|
||||
|
||||
Thanks to all the users and developers who made this release possible.
|
||||
We'll follow up with more RC releases, until the code looks production ready
|
||||
and we don't get reports of serious issues for a while.
|
||||
|
||||
A special thank you for the amount of work put into this release by:
|
||||
- Oran Agra
|
||||
- Yossi Gottlieb
|
||||
- Viktor Söderqvist
|
||||
- Yang Bodong
|
||||
- Filipe Oliveira
|
||||
- Guy Benoish
|
||||
- Itamar Haber
|
||||
- Madelyn Olson
|
||||
- Wang Yuan
|
||||
- Felipe Machado
|
||||
- Wen Hui
|
||||
- Tatsuya Arisawa
|
||||
- Jonah H. Harris
|
||||
- Raghav Muddur
|
||||
- Jim Brunner
|
||||
- Yaacov Hazan
|
||||
- Allen Farris
|
||||
- Chen Yang
|
||||
- Nitai Caro
|
||||
- sundb
|
||||
- Meir Shpilraien
|
||||
- maohuazhu
|
||||
- Valentino Geron
|
||||
- Zhao Zhao
|
||||
- Qu Chen
|
||||
- George Prekas
|
||||
- Tyson Andre
|
||||
- Uri Yagelnik
|
||||
- Michael Grunder
|
||||
- Huang Zw
|
||||
- alexronke-channeladvisor
|
||||
- Andy Pan
|
||||
- Wu Yunlong
|
||||
- Wei Kukey
|
||||
- Yoav Steinberg
|
||||
- Greg Femec
|
||||
- Uri Shachar
|
||||
- Nykolas Laurentino de Lima
|
||||
- xhe
|
||||
- zhenwei pi
|
||||
- David CARLIER
|
||||
|
||||
Migrating from 6.0 to 6.2
|
||||
=========================
|
||||
|
||||
Redis 6.2 is mostly a strict superset of 6.0, you should not have any problem
|
||||
upgrading your application from 6.0 to 6.2. However there are some small changes
|
||||
of behavior listed above, please make sure you are not badly affected by any of
|
||||
them.
|
||||
|
||||
Specifically these sections:
|
||||
* Command behavior changes
|
||||
* Other behavior changes
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Cheers,
|
||||
The Redis team
|
||||
|
||||
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
+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? */
|
||||
|
||||
+3
-1
@@ -173,7 +173,9 @@ tcp-keepalive 300
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
@@ -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 \
|
||||
"${@}"
|
||||
|
||||
+20
-11
@@ -34,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)
|
||||
@@ -93,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
|
||||
@@ -125,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)
|
||||
@@ -192,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
|
||||
|
||||
@@ -379,6 +383,8 @@ clean:
|
||||
|
||||
distclean: clean
|
||||
-(cd ../deps && $(MAKE) distclean)
|
||||
-(cd modules && $(MAKE) clean)
|
||||
-(cd ../tests/modules && $(MAKE) clean)
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
@@ -386,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)
|
||||
|
||||
|
||||
@@ -1892,10 +1892,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) {
|
||||
/* Consider information about passwords or permissions
|
||||
* to be sensitive, which will be the arguments for this
|
||||
* subcommand. */
|
||||
preventCommandLogging(c);
|
||||
/* 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))) {
|
||||
@@ -2245,6 +2247,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. */
|
||||
@@ -2264,6 +2268,7 @@ void authCommand(client *c) {
|
||||
} else {
|
||||
username = c->argv[1];
|
||||
password = c->argv[2];
|
||||
redactClientCommandArgument(c, 2);
|
||||
}
|
||||
|
||||
if (ACLAuthenticateUser(c,username,password) == C_OK) {
|
||||
|
||||
+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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+16
-16
@@ -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;
|
||||
|
||||
@@ -410,7 +410,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 +435,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 +477,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 +527,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 +566,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 +888,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 +897,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 +933,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 +1128,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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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++;
|
||||
@@ -289,6 +294,8 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = receiver;
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
@@ -301,6 +308,8 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
}
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
unblockClient(receiver);
|
||||
afterCommand(receiver);
|
||||
server.current_client = old_client;
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
decrRefCount(value);
|
||||
@@ -344,11 +353,15 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
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. */
|
||||
@@ -438,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
|
||||
@@ -466,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress,
|
||||
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, data.cow / (1024 * 1024));
|
||||
pname, cow / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-7
@@ -536,6 +536,8 @@ void clusterInit(void) {
|
||||
exit(1);
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (createSocketAcceptHandler(&server.cfd, clusterAcceptHandler) != C_OK) {
|
||||
@@ -707,6 +709,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
connNonBlock(conn);
|
||||
connEnableTcpNoDelay(conn);
|
||||
connKeepAlive(conn,server.cluster_node_timeout * 2);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
|
||||
@@ -5176,7 +5179,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;
|
||||
@@ -5361,13 +5364,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,
|
||||
@@ -5465,9 +5471,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,
|
||||
@@ -5658,10 +5665,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;
|
||||
}
|
||||
|
||||
@@ -5974,7 +5980,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;
|
||||
@@ -5988,6 +5995,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) {
|
||||
|
||||
+1
-1
@@ -726,7 +726,7 @@ void configSetCommand(client *c) {
|
||||
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
|
||||
{
|
||||
if (config->flags & SENSITIVE_CONFIG) {
|
||||
preventCommandLogging(c);
|
||||
redactClientCommandArgument(c,3);
|
||||
}
|
||||
if (!config->interface.set(config->data,o->ptr,1,&errstr)) {
|
||||
goto badfmt;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#ifdef __linux__
|
||||
#include <features.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
/* Define redis_fstat to fstat or fstat64() */
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "atomicvar.h"
|
||||
#include "latency.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <ctype.h>
|
||||
@@ -164,7 +165,7 @@ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||
}
|
||||
static void SentReplyOnKeyMiss(client *c, robj *reply){
|
||||
void SentReplyOnKeyMiss(client *c, robj *reply){
|
||||
serverAssert(sdsEncodedObject(reply));
|
||||
sds rep = reply->ptr;
|
||||
if (sdslen(rep) > 1 && rep[0] == '-'){
|
||||
@@ -576,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) {
|
||||
@@ -1437,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.
|
||||
@@ -1480,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
|
||||
@@ -1541,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;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@@ -1612,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 {
|
||||
|
||||
+9
-7
@@ -721,7 +721,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")) {
|
||||
@@ -737,11 +737,13 @@ 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");
|
||||
@@ -1809,7 +1811,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
serverLog(LL_WARNING,
|
||||
"Accessing address: %p", (void*)info->si_addr);
|
||||
}
|
||||
if (info->si_code <= SI_USER && 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -154,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;
|
||||
|
||||
|
||||
+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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+3
-2
@@ -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,7 +282,7 @@ 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 structure.
|
||||
|
||||
+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);
|
||||
|
||||
+31
-8
@@ -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,6 +922,11 @@ int lpValidateNext(unsigned char *lp, unsigned char **pp, size_t lpbytes) {
|
||||
#undef OUT_OF_RANGE
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
|
||||
+63
-11
@@ -269,6 +269,7 @@ typedef struct RedisModuleCallReply {
|
||||
long long ll; /* Reply value for integer reply. */
|
||||
struct RedisModuleCallReply *array; /* Array of sub-reply elements. */
|
||||
} val;
|
||||
list *deferred_error_list; /* list of errors in sds form or NULL */
|
||||
} RedisModuleCallReply;
|
||||
|
||||
/* Structure representing a blocked client. We get a pointer to such
|
||||
@@ -1702,6 +1703,15 @@ int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
sds proto = sdsnewlen(reply->proto, reply->protolen);
|
||||
addReplySds(c,proto);
|
||||
|
||||
/* Propagate the error list from that reply to the other client, to do some
|
||||
* post error reply handling, like statistics.
|
||||
* Note that if the original reply had an array with errors, and the module
|
||||
* replied with just a portion of the original reply, and not the entire
|
||||
* reply, the errors are currently not propagated and the errors stats
|
||||
* will not get propagated. */
|
||||
if (reply->deferred_error_list)
|
||||
deferredAfterErrorReply(c, reply->deferred_error_list);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -2274,7 +2284,10 @@ static void moduleCloseKey(RedisModuleKey *key) {
|
||||
int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx);
|
||||
if ((key->mode & REDISMODULE_WRITE) && signal)
|
||||
signalModifiedKey(key->ctx->client,key->db,key->key);
|
||||
if (key->iter) zfree(key->iter);
|
||||
if (key->iter) {
|
||||
streamIteratorStop(key->iter);
|
||||
zfree(key->iter);
|
||||
}
|
||||
RM_ZsetRangeStop(key);
|
||||
if (key && key->value && key->value->type == OBJ_STREAM &&
|
||||
key->u.stream.signalready) {
|
||||
@@ -2542,7 +2555,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
|
||||
if (newlen > curlen) {
|
||||
key->value->ptr = sdsgrowzero(key->value->ptr,newlen);
|
||||
} else if (newlen < curlen) {
|
||||
sdsrange(key->value->ptr,0,newlen-1);
|
||||
sdssubstr(key->value->ptr,0,newlen);
|
||||
/* If the string is too wasteful, reallocate it. */
|
||||
if (sdslen(key->value->ptr) < sdsavail(key->value->ptr))
|
||||
key->value->ptr = sdsRemoveFreeSpace(key->value->ptr);
|
||||
@@ -3319,6 +3332,7 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) {
|
||||
* - EDOM if the given ID was 0-0 or not greater than all other IDs in the
|
||||
* stream (only if the AUTOID flag is unset)
|
||||
* - EFBIG if the stream has reached the last possible ID
|
||||
* - ERANGE if the elements are too large to be stored.
|
||||
*/
|
||||
int RM_StreamAdd(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisModuleString **argv, long numfields) {
|
||||
/* Validate args */
|
||||
@@ -3362,8 +3376,9 @@ int RM_StreamAdd(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisM
|
||||
use_id_ptr = &use_id;
|
||||
}
|
||||
if (streamAppendItem(s, argv, numfields, &added_id, use_id_ptr) == C_ERR) {
|
||||
/* ID not greater than all existing IDs in the stream */
|
||||
errno = EDOM;
|
||||
/* Either the ID not greater than all existing IDs in the stream, or
|
||||
* the elements are too large to be stored. either way, errno is already
|
||||
* set by streamAppendItem. */
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
/* Postponed signalKeyAsReady(). Done implicitly by moduleCreateEmptyKey()
|
||||
@@ -3529,6 +3544,7 @@ int RM_StreamIteratorStop(RedisModuleKey *key) {
|
||||
errno = EBADF;
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
streamIteratorStop(key->iter);
|
||||
zfree(key->iter);
|
||||
key->iter = NULL;
|
||||
return REDISMODULE_OK;
|
||||
@@ -3751,12 +3767,16 @@ long long RM_StreamTrimByID(RedisModuleKey *key, int flags, RedisModuleStreamID
|
||||
/* Create a new RedisModuleCallReply object. The processing of the reply
|
||||
* is lazy, the object is just populated with the raw protocol and later
|
||||
* is processed as needed. Initially we just make sure to set the right
|
||||
* reply type, which is extremely cheap to do. */
|
||||
RedisModuleCallReply *moduleCreateCallReplyFromProto(RedisModuleCtx *ctx, sds proto) {
|
||||
* reply type, which is extremely cheap to do.
|
||||
* The deferred_error_list is an optional list of errors that are present
|
||||
* in the reply blob, if given, this function will take ownership on it.
|
||||
*/
|
||||
RedisModuleCallReply *moduleCreateCallReplyFromProto(RedisModuleCtx *ctx, sds proto, list *deferred_error_list) {
|
||||
RedisModuleCallReply *reply = zmalloc(sizeof(*reply));
|
||||
reply->ctx = ctx;
|
||||
reply->proto = proto;
|
||||
reply->protolen = sdslen(proto);
|
||||
reply->deferred_error_list = deferred_error_list;
|
||||
reply->flags = REDISMODULE_REPLYFLAG_TOPARSE; /* Lazy parsing. */
|
||||
switch(proto[0]) {
|
||||
case '$':
|
||||
@@ -3874,11 +3894,14 @@ void moduleFreeCallReplyRec(RedisModuleCallReply *reply, int freenested){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* For nested replies, we don't free reply->proto (which if not NULL
|
||||
* references the parent reply->proto buffer), nor the structure
|
||||
* itself which is allocated as an array of structures, and is freed
|
||||
* when the array value is released. */
|
||||
if (!(reply->flags & REDISMODULE_REPLYFLAG_NESTED)) {
|
||||
if (reply->deferred_error_list)
|
||||
listRelease(reply->deferred_error_list);
|
||||
if (reply->proto) sdsfree(reply->proto);
|
||||
zfree(reply);
|
||||
}
|
||||
@@ -4200,7 +4223,8 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
proto = sdscatlen(proto,o->buf,o->used);
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
reply = moduleCreateCallReplyFromProto(ctx,proto);
|
||||
reply = moduleCreateCallReplyFromProto(ctx,proto,c->deferred_reply_errors);
|
||||
c->deferred_reply_errors = NULL; /* now the responsibility of the reply object. */
|
||||
autoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply);
|
||||
|
||||
cleanup:
|
||||
@@ -5362,8 +5386,8 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
* reply_callback: called after a successful RedisModule_UnblockClient()
|
||||
* call in order to reply to the client and unblock it.
|
||||
*
|
||||
* timeout_callback: called when the timeout is reached in order to send an
|
||||
* error to the client.
|
||||
* timeout_callback: called when the timeout is reached or if `CLIENT UNBLOCK`
|
||||
* is invoked, in order to send an error to the client.
|
||||
*
|
||||
* free_privdata: called in order to free the private data that is passed
|
||||
* by RedisModule_UnblockClient() call.
|
||||
@@ -5380,6 +5404,12 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
* In these cases, a call to RedisModule_BlockClient() will **not** block the
|
||||
* client, but instead produce a specific error reply.
|
||||
*
|
||||
* A module that registers a timeout_callback function can also be unblocked
|
||||
* using the `CLIENT UNBLOCK` command, which will trigger the timeout callback.
|
||||
* If a callback function is not registered, then the blocked client will be
|
||||
* treated as if it is not in a blocked state and `CLIENT UNBLOCK` will return
|
||||
* a zero value.
|
||||
*
|
||||
* Measuring background time: By default the time spent in the blocked command
|
||||
* is not account for the total command duration. To include such time you should
|
||||
* use RM_BlockedClientMeasureTimeStart() and RM_BlockedClientMeasureTimeEnd() one,
|
||||
@@ -5649,6 +5679,17 @@ void moduleHandleBlockedClients(void) {
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
}
|
||||
|
||||
/* Check if the specified client can be safely timed out using
|
||||
* moduleBlockedClientTimedOut().
|
||||
*/
|
||||
int moduleBlockedClientMayTimeout(client *c) {
|
||||
if (c->btype != BLOCKED_MODULE)
|
||||
return 1;
|
||||
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
return (bc && bc->timeout_callback != NULL);
|
||||
}
|
||||
|
||||
/* Called when our client timed out. After this function unblockClient()
|
||||
* is called, and it will invalidate the blocked client. So this function
|
||||
* does not need to do any cleanup. Eventually the module will call the
|
||||
@@ -6168,7 +6209,7 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
||||
/* If the information is not available, the function will set the
|
||||
* field to zero bytes, so that when the field can't be populated the
|
||||
* function kinda remains predictable. */
|
||||
if (node->flags & CLUSTER_NODE_MASTER && node->slaveof)
|
||||
if (node->flags & CLUSTER_NODE_SLAVE && node->slaveof)
|
||||
memcpy(master_id,node->slaveof->name,REDISMODULE_NODE_ID_LEN);
|
||||
else
|
||||
memset(master_id,0,REDISMODULE_NODE_ID_LEN);
|
||||
@@ -8679,8 +8720,9 @@ sds genModulesInfoStringRenderModulesList(list *l) {
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModule *module = ln->value;
|
||||
output = sdscat(output,module->name);
|
||||
if (ln != listLast(l))
|
||||
output = sdscat(output,"|");
|
||||
}
|
||||
output = sdstrim(output,"|");
|
||||
output = sdscat(output,"]");
|
||||
return output;
|
||||
}
|
||||
@@ -9006,6 +9048,14 @@ int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc,
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Return the name of the command currently running */
|
||||
const char *RM_GetCurrentCommandName(RedisModuleCtx *ctx) {
|
||||
if (!ctx || !ctx->client || !ctx->client->cmd)
|
||||
return NULL;
|
||||
|
||||
return (const char*)ctx->client->cmd->name;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* ## Defrag API
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -9205,6 +9255,7 @@ long moduleDefragGlobals(void) {
|
||||
module->defrag_cb(&defrag_ctx);
|
||||
defragged += defrag_ctx.defragged;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
return defragged;
|
||||
}
|
||||
@@ -9466,6 +9517,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(GetServerVersion);
|
||||
REGISTER_API(GetClientCertificate);
|
||||
REGISTER_API(GetCommandKeys);
|
||||
REGISTER_API(GetCurrentCommandName);
|
||||
REGISTER_API(GetTypeMethodVersion);
|
||||
REGISTER_API(RegisterDefragFunc);
|
||||
REGISTER_API(DefragAlloc);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+30
-15
@@ -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;
|
||||
@@ -266,15 +274,6 @@ void execCommand(client *c) {
|
||||
}
|
||||
|
||||
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) ===================
|
||||
@@ -352,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) {
|
||||
|
||||
+152
-36
@@ -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));
|
||||
|
||||
@@ -159,6 +168,7 @@ client *createClient(connection *conn) {
|
||||
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);
|
||||
@@ -333,7 +343,7 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
|
||||
listAddNodeTail(c->reply, tail);
|
||||
c->reply_bytes += tail->size;
|
||||
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +418,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
|
||||
@@ -616,7 +638,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
listNodeValue(ln) = buf;
|
||||
c->reply_bytes += buf->size;
|
||||
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +650,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);
|
||||
@@ -649,14 +689,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 */
|
||||
@@ -685,6 +724,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. */
|
||||
@@ -711,11 +760,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;
|
||||
}
|
||||
|
||||
@@ -756,14 +813,13 @@ 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);
|
||||
addReplyAggregateLen(c,length,'>');
|
||||
}
|
||||
|
||||
void addReplyNull(client *c) {
|
||||
@@ -948,8 +1004,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.
|
||||
@@ -964,6 +1026,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) {
|
||||
@@ -1358,6 +1432,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
|
||||
@@ -1644,6 +1720,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)
|
||||
@@ -1663,9 +1743,6 @@ void resetClient(client *c) {
|
||||
c->flags |= CLIENT_REPLY_SKIP;
|
||||
c->flags &= ~CLIENT_REPLY_SKIP_NEXT;
|
||||
}
|
||||
|
||||
/* Always clear the prevent logging field. */
|
||||
c->flags &= ~CLIENT_PREVENT_LOGGING;
|
||||
}
|
||||
|
||||
/* This function is used when we want to re-enter the event loop but there
|
||||
@@ -1855,6 +1932,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;
|
||||
@@ -1902,6 +1983,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;
|
||||
@@ -1969,19 +2054,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 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(). */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
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
|
||||
@@ -2672,7 +2761,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");
|
||||
@@ -2967,7 +3056,8 @@ void helloCommand(client *c) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
const char *opt = c->argv[j]->ptr;
|
||||
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
|
||||
preventCommandLogging(c);
|
||||
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;
|
||||
@@ -3054,6 +3144,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. */
|
||||
@@ -3223,18 +3322,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
|
||||
@@ -3313,6 +3427,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);
|
||||
@@ -3632,7 +3747,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);
|
||||
@@ -3696,6 +3811,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
|
||||
|
||||
+18
-4
@@ -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.
|
||||
*
|
||||
@@ -836,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");
|
||||
}
|
||||
@@ -881,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
|
||||
@@ -1231,8 +1246,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;
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -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. */
|
||||
@@ -444,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)
|
||||
@@ -463,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)
|
||||
@@ -482,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 =
|
||||
@@ -505,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 =
|
||||
@@ -847,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 */
|
||||
|
||||
@@ -545,8 +545,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;
|
||||
@@ -1516,12 +1520,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
|
||||
@@ -1540,6 +1549,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,
|
||||
@@ -1560,9 +1570,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 */
|
||||
@@ -1623,10 +1636,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;
|
||||
|
||||
@@ -1663,6 +1678,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) {
|
||||
@@ -1675,8 +1691,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;
|
||||
@@ -1685,6 +1704,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();
|
||||
|
||||
@@ -1729,21 +1749,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);
|
||||
}
|
||||
@@ -1792,6 +1821,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);
|
||||
@@ -1811,7 +1842,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 ||
|
||||
@@ -1856,12 +1899,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);
|
||||
@@ -1870,6 +1912,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);
|
||||
@@ -1894,6 +1939,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);
|
||||
@@ -1923,6 +1976,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;
|
||||
@@ -1937,6 +1997,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;
|
||||
@@ -2005,7 +2072,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) {
|
||||
@@ -2094,7 +2161,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);
|
||||
@@ -2158,15 +2225,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);
|
||||
@@ -2233,7 +2318,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
|
||||
@@ -2334,6 +2424,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;
|
||||
@@ -2423,7 +2515,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
|
||||
@@ -2484,8 +2579,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);
|
||||
@@ -2506,19 +2603,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 {
|
||||
@@ -2533,10 +2631,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
|
||||
@@ -2546,18 +2641,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.
|
||||
@@ -2614,6 +2724,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():
|
||||
@@ -2691,6 +2811,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+49
-44
@@ -99,7 +99,6 @@ static struct config {
|
||||
int randomkeys_keyspacelen;
|
||||
int keepalive;
|
||||
int pipeline;
|
||||
int showerrors;
|
||||
long long start;
|
||||
long long totlatency;
|
||||
const char *title;
|
||||
@@ -307,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;
|
||||
@@ -366,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) {
|
||||
@@ -513,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);
|
||||
@@ -640,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);
|
||||
@@ -1293,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 */
|
||||
@@ -1470,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
|
||||
@@ -1573,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"
|
||||
@@ -1699,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;
|
||||
@@ -1782,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);
|
||||
@@ -1946,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);
|
||||
}
|
||||
|
||||
@@ -1974,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];
|
||||
|
||||
@@ -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++;
|
||||
|
||||
+130
-56
@@ -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;
|
||||
@@ -445,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) {
|
||||
@@ -663,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;
|
||||
}
|
||||
@@ -792,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. */
|
||||
@@ -822,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:
|
||||
@@ -844,7 +865,9 @@ static int cliConnect(int flags) {
|
||||
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);
|
||||
@@ -863,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;
|
||||
@@ -899,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);
|
||||
@@ -1127,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++) {
|
||||
@@ -1185,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++) {
|
||||
@@ -1272,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;
|
||||
@@ -1296,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)
|
||||
@@ -1308,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);
|
||||
@@ -1429,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();
|
||||
@@ -1441,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);
|
||||
@@ -1536,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")) {
|
||||
@@ -1774,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");
|
||||
@@ -1892,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"
|
||||
@@ -1990,23 +2053,32 @@ static sds *getSdsArrayFromArgv(int argc, char **argv, int quoted) {
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -5481,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'. */
|
||||
@@ -5494,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;
|
||||
@@ -6534,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. */
|
||||
@@ -6568,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 "
|
||||
@@ -6871,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;
|
||||
@@ -6985,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 */
|
||||
@@ -7045,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) {
|
||||
@@ -7150,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);
|
||||
@@ -7193,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 {
|
||||
@@ -7202,7 +7274,7 @@ static void getRDB(clusterManagerNode *node) {
|
||||
redisFree(s); /* Close the connection ASAP as fsync() may take time. */
|
||||
if (node)
|
||||
node->context = NULL;
|
||||
if (fsync(fd) == -1) {
|
||||
if (!write_to_stdout && fsync(fd) == -1) {
|
||||
fprintf(stderr,"Fail to fsync '%s': %s\n", filename, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
@@ -8203,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;
|
||||
@@ -8213,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;
|
||||
|
||||
@@ -836,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;
|
||||
@@ -1108,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);
|
||||
|
||||
+17
-12
@@ -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 {
|
||||
@@ -1303,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;
|
||||
@@ -1311,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;
|
||||
}
|
||||
@@ -1356,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;
|
||||
}
|
||||
@@ -1368,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+309
-75
@@ -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);
|
||||
@@ -339,6 +446,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,6 +480,7 @@ 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);
|
||||
t = lua_type(lua,-1);
|
||||
@@ -404,6 +523,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
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 */
|
||||
@@ -426,6 +546,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
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,6 +566,7 @@ 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);
|
||||
t = lua_type(lua,-1);
|
||||
@@ -556,6 +678,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;
|
||||
@@ -655,6 +781,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1039,52 +1172,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.
|
||||
@@ -1108,8 +1309,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
|
||||
@@ -1243,6 +1447,7 @@ void scriptingInit(int setup) {
|
||||
* 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"
|
||||
@@ -1270,10 +1475,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;
|
||||
}
|
||||
@@ -1304,7 +1511,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);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
@@ -1385,14 +1594,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",
|
||||
@@ -1400,20 +1602,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
|
||||
@@ -1427,7 +1621,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);
|
||||
|
||||
@@ -1541,7 +1735,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
|
||||
@@ -1559,7 +1753,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));
|
||||
}
|
||||
|
||||
@@ -1578,7 +1772,8 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
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);
|
||||
@@ -1688,6 +1883,9 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1695,6 +1893,9 @@ 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
|
||||
@@ -2073,7 +2274,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;
|
||||
@@ -2090,7 +2292,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;
|
||||
@@ -2099,12 +2301,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;
|
||||
@@ -2114,6 +2320,8 @@ sds *ldbReplParseCommand(int *argcp) {
|
||||
return argv;
|
||||
|
||||
protoerr:
|
||||
*err = protocol_error;
|
||||
keep_reading:
|
||||
sdsfreesplitres(argv,argc);
|
||||
sdsfree(copy);
|
||||
return NULL;
|
||||
@@ -2546,6 +2754,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 */
|
||||
@@ -2602,12 +2821,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
|
||||
@@ -2617,6 +2841,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. */
|
||||
@@ -2729,7 +2962,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) {
|
||||
@@ -2759,6 +2992,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;
|
||||
@@ -759,6 +759,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 +785,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 +797,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'. */
|
||||
@@ -1353,6 +1358,18 @@ int sdsTest(int argc, char **argv, int accurate) {
|
||||
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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
+60
-24
@@ -593,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;
|
||||
|
||||
@@ -606,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);
|
||||
@@ -639,6 +645,15 @@ 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];
|
||||
@@ -1296,7 +1311,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. */
|
||||
@@ -1424,7 +1439,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);
|
||||
@@ -1487,8 +1502,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);
|
||||
@@ -1497,8 +1514,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;
|
||||
@@ -1629,7 +1645,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.
|
||||
@@ -2402,7 +2418,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 +2430,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,7 +2441,21 @@ 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 && !link->cc->err) anetCloexec(link->cc->c.fd);
|
||||
if (!link->cc) {
|
||||
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to establish connection");
|
||||
@@ -3175,11 +3209,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",
|
||||
@@ -3682,11 +3718,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'",
|
||||
@@ -3775,8 +3811,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;
|
||||
@@ -4119,16 +4154,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,
|
||||
(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;
|
||||
}
|
||||
}
|
||||
@@ -4700,6 +4735,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;
|
||||
|
||||
+126
-53
@@ -706,7 +706,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"auth",authCommand,-2,
|
||||
"no-auth no-script ok-loading ok-stale fast no-monitor no-slowlog @connection",
|
||||
"no-auth no-script ok-loading ok-stale fast @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
/* We don't allow PING during loading since in Redis PING is used as
|
||||
@@ -749,7 +749,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"exec",execCommand,1,
|
||||
"no-script no-monitor no-slowlog ok-loading ok-stale @transaction",
|
||||
"no-script no-slowlog ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"discard",discardCommand,1,
|
||||
@@ -870,7 +870,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"migrate",migrateCommand,-6,
|
||||
"write random @keyspace @dangerous",
|
||||
0,migrateGetKeys,0,0,0,0,0,0},
|
||||
0,migrateGetKeys,3,3,1,0,0,0},
|
||||
|
||||
{"asking",askingCommand,1,
|
||||
"fast @keyspace",
|
||||
@@ -901,17 +901,21 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"hello",helloCommand,-1,
|
||||
"no-auth no-script fast no-monitor ok-loading ok-stale @connection",
|
||||
"no-auth no-script fast ok-loading ok-stale @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
/* EVAL can modify the dataset, however it is not flagged as a write
|
||||
* command since we do the check while running commands from Lua. */
|
||||
* command since we do the check while running commands from Lua.
|
||||
*
|
||||
* EVAL and EVALSHA also feed monitors before the commands are executed,
|
||||
* as opposed to after.
|
||||
*/
|
||||
{"eval",evalCommand,-3,
|
||||
"no-script may-replicate @scripting",
|
||||
"no-script no-monitor may-replicate @scripting",
|
||||
0,evalGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"evalsha",evalShaCommand,-3,
|
||||
"no-script may-replicate @scripting",
|
||||
"no-script no-monitor may-replicate @scripting",
|
||||
0,evalGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"slowlog",slowlogCommand,-2,
|
||||
@@ -1839,6 +1843,7 @@ void clientsCron(void) {
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
if (clientsCronTrackExpansiveClients(c, curr_peak_mem_usage_slot)) continue;
|
||||
if (clientsCronTrackClientsMemUsage(c)) continue;
|
||||
if (closeClientOnOutputBufferLimitReached(c, 0)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,18 +1903,8 @@ void databasesCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL).
|
||||
*
|
||||
* This function should be fast because it is called at every command execution
|
||||
* in call(), so it is possible to decide if to update the daylight saving
|
||||
* info or not using the 'update_daylight_info' argument. Normally we update
|
||||
* such info only when calling this function from serverCron() but not when
|
||||
* calling it from call(). */
|
||||
void updateCachedTime(int update_daylight_info) {
|
||||
server.ustime = ustime();
|
||||
static inline void updateCachedTimeWithUs(int update_daylight_info, const long long ustime) {
|
||||
server.ustime = ustime;
|
||||
server.mstime = server.ustime / 1000;
|
||||
time_t unixtime = server.mstime / 1000;
|
||||
atomicSet(server.unixtime, unixtime);
|
||||
@@ -1927,6 +1922,21 @@ void updateCachedTime(int update_daylight_info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL).
|
||||
*
|
||||
* This function should be fast because it is called at every command execution
|
||||
* in call(), so it is possible to decide if to update the daylight saving
|
||||
* info or not using the 'update_daylight_info' argument. Normally we update
|
||||
* such info only when calling this function from serverCron() but not when
|
||||
* calling it from call(). */
|
||||
void updateCachedTime(int update_daylight_info) {
|
||||
const long long us = ustime();
|
||||
updateCachedTimeWithUs(update_daylight_info, us);
|
||||
}
|
||||
|
||||
void checkChildrenDone(void) {
|
||||
int statloc = 0;
|
||||
pid_t pid;
|
||||
@@ -2426,6 +2436,11 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
* our clients. */
|
||||
updateFailoverStatus();
|
||||
|
||||
/* Since we rely on current_client to send scheduled invalidation messages
|
||||
* we have to flush them after each command, so when we get here, the list
|
||||
* must be empty. */
|
||||
serverAssert(listLength(server.tracking_pending_keys) == 0);
|
||||
|
||||
/* Send the invalidation messages to clients participating to the
|
||||
* client side caching protocol in broadcasting (BCAST) mode. */
|
||||
trackingBroadcastInvalidationMessages();
|
||||
@@ -2603,6 +2618,7 @@ void createSharedObjects(void) {
|
||||
shared.getack = createStringObject("GETACK",6);
|
||||
shared.special_asterick = createStringObject("*",1);
|
||||
shared.special_equals = createStringObject("=",1);
|
||||
shared.redacted = makeObjectShared(createStringObject("(redacted)",10));
|
||||
|
||||
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
|
||||
shared.integers[j] =
|
||||
@@ -2614,6 +2630,10 @@ void createSharedObjects(void) {
|
||||
sdscatprintf(sdsempty(),"*%d\r\n",j));
|
||||
shared.bulkhdr[j] = createObject(OBJ_STRING,
|
||||
sdscatprintf(sdsempty(),"$%d\r\n",j));
|
||||
shared.maphdr[j] = createObject(OBJ_STRING,
|
||||
sdscatprintf(sdsempty(),"%%%d\r\n",j));
|
||||
shared.sethdr[j] = createObject(OBJ_STRING,
|
||||
sdscatprintf(sdsempty(),"~%d\r\n",j));
|
||||
}
|
||||
/* The following two shared objects, minstring and maxstrings, are not
|
||||
* actually used for their value but as a special object meaning
|
||||
@@ -2758,6 +2778,10 @@ void initServerConfig(void) {
|
||||
* Redis 5. However it is possible to revert it via redis.conf. */
|
||||
server.lua_always_replicate_commands = 1;
|
||||
|
||||
/* Client Pause related */
|
||||
server.client_pause_type = CLIENT_PAUSE_OFF;
|
||||
server.client_pause_end_time = 0;
|
||||
|
||||
initConfigValues();
|
||||
}
|
||||
|
||||
@@ -3150,6 +3174,7 @@ void initServer(void) {
|
||||
server.current_client = NULL;
|
||||
server.errors = raxNew();
|
||||
server.fixed_time_expire = 0;
|
||||
server.in_nested_call = 0;
|
||||
server.clients = listCreate();
|
||||
server.clients_index = raxNew();
|
||||
server.clients_to_close = listCreate();
|
||||
@@ -3162,6 +3187,7 @@ void initServer(void) {
|
||||
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
|
||||
server.unblocked_clients = listCreate();
|
||||
server.ready_keys = listCreate();
|
||||
server.tracking_pending_keys = listCreate();
|
||||
server.clients_waiting_acks = listCreate();
|
||||
server.get_ack_from_slaves = 0;
|
||||
server.client_pause_type = 0;
|
||||
@@ -3193,11 +3219,13 @@ void initServer(void) {
|
||||
/* Open the TCP listening socket for the user commands. */
|
||||
if (server.port != 0 &&
|
||||
listenToPort(server.port,&server.ipfd) == C_ERR) {
|
||||
/* Note: the following log text is matched by the test suite. */
|
||||
serverLog(LL_WARNING, "Failed listening on port %u (TCP), aborting.", server.port);
|
||||
exit(1);
|
||||
}
|
||||
if (server.tls_port != 0 &&
|
||||
listenToPort(server.tls_port,&server.tlsfd) == C_ERR) {
|
||||
/* Note: the following log text is matched by the test suite. */
|
||||
serverLog(LL_WARNING, "Failed listening on port %u (TLS), aborting.", server.tls_port);
|
||||
exit(1);
|
||||
}
|
||||
@@ -3624,12 +3652,6 @@ void preventCommandPropagation(client *c) {
|
||||
c->flags |= CLIENT_PREVENT_PROP;
|
||||
}
|
||||
|
||||
/* Avoid logging any information about this client's arguments
|
||||
* since they contain sensitive information. */
|
||||
void preventCommandLogging(client *c) {
|
||||
c->flags |= CLIENT_PREVENT_LOGGING;
|
||||
}
|
||||
|
||||
/* AOF specific version of preventCommandPropagation(). */
|
||||
void preventCommandAOF(client *c) {
|
||||
c->flags |= CLIENT_PREVENT_AOF_PROP;
|
||||
@@ -3643,7 +3665,7 @@ void preventCommandReplication(client *c) {
|
||||
/* Log the last command a client executed into the slowlog. */
|
||||
void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t duration) {
|
||||
/* Some commands may contain sensitive data that should not be available in the slowlog. */
|
||||
if ((c->flags & CLIENT_PREVENT_LOGGING) || (cmd->flags & CMD_SKIP_SLOWLOG))
|
||||
if (cmd->flags & CMD_SKIP_SLOWLOG)
|
||||
return;
|
||||
|
||||
/* If command argument vector was rewritten, use the original
|
||||
@@ -3692,22 +3714,10 @@ void slowlogPushCurrentCommand(client *c, struct redisCommand *cmd, ustime_t dur
|
||||
*/
|
||||
void call(client *c, int flags) {
|
||||
long long dirty;
|
||||
monotime call_timer;
|
||||
int client_old_flags = c->flags;
|
||||
struct redisCommand *real_cmd = c->cmd;
|
||||
static long long prev_err_count;
|
||||
|
||||
server.fixed_time_expire++;
|
||||
|
||||
/* Send the command to clients in MONITOR mode if applicable.
|
||||
* Administrative commands are considered too dangerous to be shown. */
|
||||
if (listLength(server.monitors) &&
|
||||
!server.loading &&
|
||||
!(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN)))
|
||||
{
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
/* Initialization: clear the flags that must be set by the command on
|
||||
* demand, and initialize the array for additional commands propagation. */
|
||||
c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
|
||||
@@ -3717,10 +3727,31 @@ void call(client *c, int flags) {
|
||||
/* Call the command. */
|
||||
dirty = server.dirty;
|
||||
prev_err_count = server.stat_total_error_replies;
|
||||
updateCachedTime(0);
|
||||
elapsedStart(&call_timer);
|
||||
|
||||
const long long call_timer = ustime();
|
||||
|
||||
/* Update cache time, in case we have nested calls we want to
|
||||
* update only on the first call*/
|
||||
if (server.fixed_time_expire++ == 0) {
|
||||
updateCachedTimeWithUs(0,call_timer);
|
||||
}
|
||||
|
||||
monotime monotonic_start = 0;
|
||||
if (monotonicGetType() == MONOTONIC_CLOCK_HW)
|
||||
monotonic_start = getMonotonicUs();
|
||||
|
||||
server.in_nested_call++;
|
||||
c->cmd->proc(c);
|
||||
const long duration = elapsedUs(call_timer);
|
||||
server.in_nested_call--;
|
||||
|
||||
/* In order to avoid performance implication due to querying the clock using a system call 3 times,
|
||||
* we use a monotonic clock, when we are sure its cost is very low, and fall back to non-monotonic call otherwise. */
|
||||
ustime_t duration;
|
||||
if (monotonicGetType() == MONOTONIC_CLOCK_HW)
|
||||
duration = getMonotonicUs() - monotonic_start;
|
||||
else
|
||||
duration = ustime() - call_timer;
|
||||
|
||||
c->duration = duration;
|
||||
dirty = server.dirty-dirty;
|
||||
if (dirty < 0) dirty = 0;
|
||||
@@ -3773,6 +3804,14 @@ void call(client *c, int flags) {
|
||||
if ((flags & CMD_CALL_SLOWLOG) && !(c->flags & CLIENT_BLOCKED))
|
||||
slowlogPushCurrentCommand(c, real_cmd, duration);
|
||||
|
||||
/* Send the command to clients in MONITOR mode if applicable.
|
||||
* Administrative commands are considered too dangerous to be shown. */
|
||||
if (!(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN))) {
|
||||
robj **argv = c->original_argv ? c->original_argv : c->argv;
|
||||
int argc = c->original_argv ? c->original_argc : c->argc;
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,argv,argc);
|
||||
}
|
||||
|
||||
/* Clear the original argv.
|
||||
* If the client is blocked we will handle slowlog when it is unblocked. */
|
||||
if (!(c->flags & CLIENT_BLOCKED))
|
||||
@@ -3891,6 +3930,9 @@ void call(client *c, int flags) {
|
||||
size_t zmalloc_used = zmalloc_used_memory();
|
||||
if (zmalloc_used > server.stat_peak_memory)
|
||||
server.stat_peak_memory = zmalloc_used;
|
||||
|
||||
/* Do some maintenance job and cleanup */
|
||||
afterCommand(c);
|
||||
}
|
||||
|
||||
/* Used when a command that is ready for execution needs to be rejected, due to
|
||||
@@ -3935,6 +3977,14 @@ static int cmdHasMovableKeys(struct redisCommand *cmd) {
|
||||
cmd->flags & CMD_MODULE_GETKEYS;
|
||||
}
|
||||
|
||||
/* This is called after a command in call, we can do some maintenance job in it. */
|
||||
void afterCommand(client *c) {
|
||||
UNUSED(c);
|
||||
/* Flush pending invalidation messages only when we are not in nested call.
|
||||
* So the messages are not interleaved with transaction response. */
|
||||
if (!server.in_nested_call) trackingHandlePendingKeyInvalidations();
|
||||
}
|
||||
|
||||
/* If this function gets called we already read a whole
|
||||
* command, arguments are in the client argv/argc fields.
|
||||
* processCommand() execute the command or prepare the
|
||||
@@ -3985,6 +4035,8 @@ int processCommand(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int is_read_command = (c->cmd->flags & CMD_READONLY) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_READONLY));
|
||||
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
|
||||
int is_denyoom_command = (c->cmd->flags & CMD_DENYOOM) ||
|
||||
@@ -3996,13 +4048,8 @@ int processCommand(client *c) {
|
||||
int is_may_replicate_command = (c->cmd->flags & (CMD_WRITE | CMD_MAY_REPLICATE)) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & (CMD_WRITE | CMD_MAY_REPLICATE)));
|
||||
|
||||
/* 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;
|
||||
if (auth_required) {
|
||||
/* AUTH and HELLO and no auth modules are valid even in
|
||||
if (authRequired(c)) {
|
||||
/* AUTH and HELLO and no auth commands are valid even in
|
||||
* non-authenticated state. */
|
||||
if (!(c->cmd->flags & CMD_NO_AUTH)) {
|
||||
rejectCommand(c,shared.noautherr);
|
||||
@@ -4074,6 +4121,13 @@ int processCommand(client *c) {
|
||||
* propagation of DELs due to eviction. */
|
||||
if (server.maxmemory && !server.lua_timedout) {
|
||||
int out_of_memory = (performEvictions() == EVICT_FAIL);
|
||||
|
||||
/* performEvictions may evict keys, so we need flush pending tracking
|
||||
* invalidation keys. If we don't do this, we may get an invalidation
|
||||
* message after we perform operation on the key, where in fact this
|
||||
* message belongs to the old value of the key before it gets evicted.*/
|
||||
trackingHandlePendingKeyInvalidations();
|
||||
|
||||
/* performEvictions may flush slave output buffers. This may result
|
||||
* in a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) return C_ERR;
|
||||
@@ -4194,7 +4248,7 @@ int processCommand(client *c) {
|
||||
c->cmd->proc != discardCommand &&
|
||||
c->cmd->proc != watchCommand &&
|
||||
c->cmd->proc != unwatchCommand &&
|
||||
c->cmd->proc != resetCommand &&
|
||||
c->cmd->proc != resetCommand &&
|
||||
!(c->cmd->proc == shutdownCommand &&
|
||||
c->argc == 2 &&
|
||||
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
|
||||
@@ -4206,6 +4260,14 @@ int processCommand(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Prevent a replica from sending commands that access the keyspace.
|
||||
* The main objective here is to prevent abuse of client pause check
|
||||
* from which replicas are exempt. */
|
||||
if ((c->flags & CLIENT_SLAVE) && (is_may_replicate_command || is_write_command || is_read_command)) {
|
||||
rejectCommandFormat(c, "Replica can't interract with the keyspace");
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* If the server is paused, block the client until
|
||||
* the pause has ended. Replicas are never paused. */
|
||||
if (!(c->flags & CLIENT_SLAVE) &&
|
||||
@@ -4662,6 +4724,7 @@ sds genRedisInfoString(const char *section) {
|
||||
"redis_mode:%s\r\n"
|
||||
"os:%s %s %s\r\n"
|
||||
"arch_bits:%i\r\n"
|
||||
"monotonic_clock:%s\r\n"
|
||||
"multiplexing_api:%s\r\n"
|
||||
"atomicvar_api:%s\r\n"
|
||||
"gcc_version:%i.%i.%i\r\n"
|
||||
@@ -4685,6 +4748,7 @@ sds genRedisInfoString(const char *section) {
|
||||
mode,
|
||||
name.sysname, name.release, name.machine,
|
||||
server.arch_bits,
|
||||
monotonicInfoString(),
|
||||
aeGetApiName(),
|
||||
REDIS_ATOMIC_API,
|
||||
#ifdef __GNUC__
|
||||
@@ -5075,11 +5139,15 @@ sds genRedisInfoString(const char *section) {
|
||||
server.masterhost == NULL ? "master" : "slave");
|
||||
if (server.masterhost) {
|
||||
long long slave_repl_offset = 1;
|
||||
long long slave_read_repl_offset = 1;
|
||||
|
||||
if (server.master)
|
||||
if (server.master) {
|
||||
slave_repl_offset = server.master->reploff;
|
||||
else if (server.cached_master)
|
||||
slave_read_repl_offset = server.master->read_reploff;
|
||||
} else if (server.cached_master) {
|
||||
slave_repl_offset = server.cached_master->reploff;
|
||||
slave_read_repl_offset = server.cached_master->read_reploff;
|
||||
}
|
||||
|
||||
info = sdscatprintf(info,
|
||||
"master_host:%s\r\n"
|
||||
@@ -5087,6 +5155,7 @@ sds genRedisInfoString(const char *section) {
|
||||
"master_link_status:%s\r\n"
|
||||
"master_last_io_seconds_ago:%d\r\n"
|
||||
"master_sync_in_progress:%d\r\n"
|
||||
"slave_read_repl_offset:%lld\r\n"
|
||||
"slave_repl_offset:%lld\r\n"
|
||||
,server.masterhost,
|
||||
server.masterport,
|
||||
@@ -5095,6 +5164,7 @@ sds genRedisInfoString(const char *section) {
|
||||
server.master ?
|
||||
((int)(server.unixtime-server.master->lastinteraction)) : -1,
|
||||
server.repl_state == REPL_STATE_TRANSFER,
|
||||
slave_read_repl_offset,
|
||||
slave_repl_offset
|
||||
);
|
||||
|
||||
@@ -6187,9 +6257,12 @@ int main(int argc, char **argv) {
|
||||
setlocale(LC_COLLATE,"");
|
||||
tzset(); /* Populates 'timezone' global. */
|
||||
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
|
||||
srand(time(NULL)^getpid());
|
||||
srandom(time(NULL)^getpid());
|
||||
|
||||
/* To achieve entropy, in case of containers, their time() and getpid() can
|
||||
* be the same. But value of tv_usec is fast enough to make the difference */
|
||||
gettimeofday(&tv,NULL);
|
||||
srand(time(NULL)^getpid()^tv.tv_usec);
|
||||
srandom(time(NULL)^getpid()^tv.tv_usec);
|
||||
init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
|
||||
crc64_init();
|
||||
|
||||
|
||||
+30
-9
@@ -99,6 +99,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)
|
||||
@@ -279,7 +280,6 @@ 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_PREVENT_LOGGING (1ULL<<43) /* Prevent logging of command to slowlog */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@@ -883,6 +883,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. */
|
||||
@@ -986,11 +987,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;
|
||||
};
|
||||
|
||||
@@ -1228,6 +1231,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 */
|
||||
@@ -1509,6 +1513,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;
|
||||
@@ -1571,7 +1576,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
|
||||
@@ -1779,6 +1785,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);
|
||||
@@ -1794,7 +1801,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);
|
||||
@@ -1840,6 +1847,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);
|
||||
@@ -1851,6 +1859,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);
|
||||
@@ -1864,9 +1873,10 @@ 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);
|
||||
@@ -1894,6 +1904,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, ...)
|
||||
@@ -1909,8 +1920,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);
|
||||
@@ -1943,6 +1957,7 @@ 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);
|
||||
@@ -1967,6 +1982,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);
|
||||
@@ -2167,7 +2184,7 @@ 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 in_flags, int *out_flags, double *newscore);
|
||||
@@ -2211,11 +2228,12 @@ void redisOpArrayInit(redisOpArray *oa);
|
||||
void redisOpArrayFree(redisOpArray *oa);
|
||||
void forceCommandPropagation(client *c, int flags);
|
||||
void preventCommandPropagation(client *c);
|
||||
void preventCommandLogging(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, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
@@ -2316,7 +2334,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);
|
||||
@@ -2330,6 +2350,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
|
||||
|
||||
+11
-6
@@ -39,17 +39,22 @@
|
||||
* as their string length can be queried in constant time. */
|
||||
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
int i;
|
||||
size_t sum = 0;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
|
||||
|
||||
for (i = start; i <= end; i++) {
|
||||
if (sdsEncodedObject(argv[i]) &&
|
||||
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
|
||||
{
|
||||
if (!sdsEncodedObject(argv[i]))
|
||||
continue;
|
||||
size_t len = sdslen(argv[i]->ptr);
|
||||
if (len > server.hash_max_ziplist_value) {
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
sum += len;
|
||||
}
|
||||
if (!ziplistSafeToAdd(o->ptr, sum))
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
}
|
||||
|
||||
/* Get the value from a ziplist encoded hash, identified by field.
|
||||
@@ -986,7 +991,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
int uniq = 1;
|
||||
robj *hash;
|
||||
|
||||
if ((hash = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
if ((hash = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray))
|
||||
== NULL || checkType(c,hash,OBJ_HASH)) return;
|
||||
size = hashTypeLength(hash);
|
||||
|
||||
@@ -1175,7 +1180,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
}
|
||||
}
|
||||
|
||||
/* HRANDFIELD [<count> WITHVALUES] */
|
||||
/* HRANDFIELD key [<count> [WITHVALUES]] */
|
||||
void hrandfieldCommand(client *c) {
|
||||
long l;
|
||||
int withvalues = 0;
|
||||
|
||||
+38
-7
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#define LIST_MAX_ITEM_SIZE ((1ull<<32)-1024)
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* List API
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -224,6 +226,13 @@ robj *listTypeDup(robj *o) {
|
||||
void pushGenericCommand(client *c, int where, int xx) {
|
||||
int j;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
|
||||
if (checkType(c,lobj,OBJ_LIST)) return;
|
||||
if (!lobj) {
|
||||
@@ -287,6 +296,11 @@ void linsertCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sdslen(c->argv[4]->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,subject,OBJ_LIST)) return;
|
||||
|
||||
@@ -354,6 +368,11 @@ void lsetCommand(client *c) {
|
||||
long index;
|
||||
robj *value = c->argv[3];
|
||||
|
||||
if (sdslen(value->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
@@ -437,6 +456,7 @@ void listElementsRemoved(client *c, robj *key, int where, robj *o, long count) {
|
||||
* optional count may be provided as the third argument of the client's
|
||||
* command. */
|
||||
void popGenericCommand(client *c, int where) {
|
||||
int hascount = (c->argc == 3);
|
||||
long count = 0;
|
||||
robj *value;
|
||||
|
||||
@@ -444,21 +464,22 @@ void popGenericCommand(client *c, int where) {
|
||||
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
|
||||
c->cmd->name);
|
||||
return;
|
||||
} else if (c->argc == 3) {
|
||||
} else if (hascount) {
|
||||
/* Parse the optional count argument. */
|
||||
if (getPositiveLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK)
|
||||
return;
|
||||
if (count == 0) {
|
||||
/* Fast exit path. */
|
||||
addReplyNullArray(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
robj *o = lookupKeyWriteOrReply(c, c->argv[1], shared.null[c->resp]);
|
||||
robj *o = lookupKeyWriteOrReply(c, c->argv[1], hascount ? shared.nullarray[c->resp]: shared.null[c->resp]);
|
||||
if (o == NULL || checkType(c, o, OBJ_LIST))
|
||||
return;
|
||||
|
||||
if (hascount && !count) {
|
||||
/* Fast exit path. */
|
||||
addReply(c,shared.emptyarray);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
/* Pop a single element. This is POP's original behavior that replies
|
||||
* with a bulk string. */
|
||||
@@ -576,6 +597,11 @@ void lposCommand(client *c) {
|
||||
int direction = LIST_TAIL;
|
||||
long rank = 1, count = -1, maxlen = 0; /* Count -1: option not given. */
|
||||
|
||||
if (sdslen(ele->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse the optional arguments. */
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
@@ -671,6 +697,11 @@ void lremCommand(client *c) {
|
||||
long toremove;
|
||||
long removed = 0;
|
||||
|
||||
if (sdslen(obj->ptr) > LIST_MAX_ITEM_SIZE) {
|
||||
addReplyError(c, "Element too large");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
|
||||
+34
-14
@@ -66,7 +66,10 @@ int setTypeAdd(robj *subject, sds value) {
|
||||
if (success) {
|
||||
/* Convert to regular set when the intset contains
|
||||
* too many entries. */
|
||||
if (intsetLen(subject->ptr) > server.set_max_intset_entries)
|
||||
size_t max_entries = server.set_max_intset_entries;
|
||||
/* limit to 1G entries due to intset internals. */
|
||||
if (max_entries >= 1<<30) max_entries = 1<<30;
|
||||
if (intsetLen(subject->ptr) > max_entries)
|
||||
setTypeConvert(subject,OBJ_ENCODING_HT);
|
||||
return 1;
|
||||
}
|
||||
@@ -392,12 +395,12 @@ void smoveCommand(client *c) {
|
||||
}
|
||||
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
|
||||
/* An extra key has changed when ele was successfully added to dstset */
|
||||
if (setTypeAdd(dstset,ele->ptr)) {
|
||||
server.dirty++;
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[2],c->db->id);
|
||||
}
|
||||
addReply(c,shared.cone);
|
||||
@@ -858,24 +861,17 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
int64_t intobj;
|
||||
void *replylen = NULL;
|
||||
unsigned long j, cardinality = 0;
|
||||
int encoding;
|
||||
int encoding, empty = 0;
|
||||
|
||||
for (j = 0; j < setnum; j++) {
|
||||
robj *setobj = dstkey ?
|
||||
lookupKeyWrite(c->db,setkeys[j]) :
|
||||
lookupKeyRead(c->db,setkeys[j]);
|
||||
if (!setobj) {
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
addReply(c,shared.emptyset[c->resp]);
|
||||
}
|
||||
return;
|
||||
/* A NULL is considered an empty set */
|
||||
empty += 1;
|
||||
sets[j] = NULL;
|
||||
continue;
|
||||
}
|
||||
if (checkType(c,setobj,OBJ_SET)) {
|
||||
zfree(sets);
|
||||
@@ -883,6 +879,24 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
}
|
||||
sets[j] = setobj;
|
||||
}
|
||||
|
||||
/* Set intersection with an empty set always results in an empty set.
|
||||
* Return ASAP if there is an empty set. */
|
||||
if (empty > 0) {
|
||||
zfree(sets);
|
||||
if (dstkey) {
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c,c->db,dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",dstkey,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
addReply(c,shared.emptyset[c->resp]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sort sets from the smallest to largest, this will improve our
|
||||
* algorithm's performance */
|
||||
qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);
|
||||
@@ -976,10 +990,12 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
zfree(sets);
|
||||
}
|
||||
|
||||
/* SINTER key [key ...] */
|
||||
void sinterCommand(client *c) {
|
||||
sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
|
||||
}
|
||||
|
||||
/* SINTERSTORE destination key [key ...] */
|
||||
void sinterstoreCommand(client *c) {
|
||||
sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]);
|
||||
}
|
||||
@@ -1149,18 +1165,22 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
zfree(sets);
|
||||
}
|
||||
|
||||
/* SUNION key [key ...] */
|
||||
void sunionCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_UNION);
|
||||
}
|
||||
|
||||
/* SUNIONSTORE destination key [key ...] */
|
||||
void sunionstoreCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_UNION);
|
||||
}
|
||||
|
||||
/* SDIFF key [key ...] */
|
||||
void sdiffCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_DIFF);
|
||||
}
|
||||
|
||||
/* SDIFFSTORE destination key [key ...] */
|
||||
void sdiffstoreCommand(client *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_DIFF);
|
||||
}
|
||||
|
||||
+55
-23
@@ -47,6 +47,12 @@
|
||||
* setting stream_node_max_bytes to a huge number. */
|
||||
#define STREAM_LISTPACK_MAX_PRE_ALLOCATE 4096
|
||||
|
||||
/* Don't let listpacks grow too big, even if the user config allows it.
|
||||
* doing so can lead to an overflow (trying to store more than 32bit length
|
||||
* into the listpack header), or actually an assertion since lpInsert
|
||||
* will return NULL. */
|
||||
#define STREAM_LISTPACK_MAX_SIZE (1<<30)
|
||||
|
||||
void streamFreeCG(streamCG *cg);
|
||||
void streamFreeNACK(streamNACK *na);
|
||||
size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer);
|
||||
@@ -433,8 +439,11 @@ void streamGetEdgeID(stream *s, int first, streamID *edge_id)
|
||||
*
|
||||
* The function returns C_OK if the item was added, this is always true
|
||||
* if the ID was generated by the function. However the function may return
|
||||
* C_ERR if an ID was given via 'use_id', but adding it failed since the
|
||||
* current top ID is greater or equal. */
|
||||
* C_ERR in several cases:
|
||||
* 1. If an ID was given via 'use_id', but adding it failed since the
|
||||
* current top ID is greater or equal. errno will be set to EDOM.
|
||||
* 2. If a size of a single element or the sum of the elements is too big to
|
||||
* be stored into the stream. errno will be set to ERANGE. */
|
||||
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
||||
|
||||
/* Generate the new entry ID. */
|
||||
@@ -448,7 +457,23 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* or return an error. Automatically generated IDs might
|
||||
* overflow (and wrap-around) when incrementing the sequence
|
||||
part. */
|
||||
if (streamCompareID(&id,&s->last_id) <= 0) return C_ERR;
|
||||
if (streamCompareID(&id,&s->last_id) <= 0) {
|
||||
errno = EDOM;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Avoid overflow when trying to add an element to the stream (listpack
|
||||
* can only host up to 32bit length sttrings, and also a total listpack size
|
||||
* can't be bigger than 32bit length. */
|
||||
size_t totelelen = 0;
|
||||
for (int64_t i = 0; i < numfields*2; i++) {
|
||||
sds ele = argv[i]->ptr;
|
||||
totelelen += sdslen(ele);
|
||||
}
|
||||
if (totelelen > STREAM_LISTPACK_MAX_SIZE) {
|
||||
errno = ERANGE;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Add the new entry. */
|
||||
raxIterator ri;
|
||||
@@ -507,9 +532,10 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* if we need to switch to the next one. 'lp' will be set to NULL if
|
||||
* the current node is full. */
|
||||
if (lp != NULL) {
|
||||
if (server.stream_node_max_bytes &&
|
||||
lp_bytes >= server.stream_node_max_bytes)
|
||||
{
|
||||
size_t node_max_bytes = server.stream_node_max_bytes;
|
||||
if (node_max_bytes == 0 || node_max_bytes > STREAM_LISTPACK_MAX_SIZE)
|
||||
node_max_bytes = STREAM_LISTPACK_MAX_SIZE;
|
||||
if (lp_bytes + totelelen >= node_max_bytes) {
|
||||
lp = NULL;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
unsigned char *lp_ele = lpFirst(lp);
|
||||
@@ -702,16 +728,16 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args) {
|
||||
|
||||
int64_t deleted = 0;
|
||||
while (raxNext(&ri)) {
|
||||
/* Check if we exceeded the amount of work we could do */
|
||||
if (limit && deleted >= limit)
|
||||
break;
|
||||
|
||||
if (trim_strategy == TRIM_STRATEGY_MAXLEN && s->length <= maxlen)
|
||||
break;
|
||||
|
||||
unsigned char *lp = ri.data, *p = lpFirst(lp);
|
||||
int64_t entries = lpGetInteger(p);
|
||||
|
||||
/* Check if we exceeded the amount of work we could do */
|
||||
if (limit && (deleted + entries) > limit)
|
||||
break;
|
||||
|
||||
/* Check if we can remove the whole node. */
|
||||
int remove_node;
|
||||
streamID master_id = {0}; /* For MINID */
|
||||
@@ -763,13 +789,13 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args) {
|
||||
* update it after (and if) we actually remove the entry */
|
||||
unsigned char *pcopy = p;
|
||||
|
||||
int flags = lpGetInteger(p);
|
||||
int64_t flags = lpGetInteger(p);
|
||||
p = lpNext(lp, p); /* Skip flags. */
|
||||
int to_skip;
|
||||
int64_t to_skip;
|
||||
|
||||
int ms_delta = lpGetInteger(p);
|
||||
int64_t ms_delta = lpGetInteger(p);
|
||||
p = lpNext(lp, p); /* Skip ID ms delta */
|
||||
int seq_delta = lpGetInteger(p);
|
||||
int64_t seq_delta = lpGetInteger(p);
|
||||
p = lpNext(lp, p); /* Skip ID seq delta */
|
||||
|
||||
streamID currid = {0}; /* For MINID */
|
||||
@@ -1101,7 +1127,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
* the first entry emitted for this listpack, then we already
|
||||
* emitted the current entry, and have to go back to the previous
|
||||
* one. */
|
||||
int lp_count = lpGetInteger(si->lp_ele);
|
||||
int64_t lp_count = lpGetInteger(si->lp_ele);
|
||||
while(lp_count--) si->lp_ele = lpPrev(si->lp,si->lp_ele);
|
||||
/* Seek lp-count of prev entry. */
|
||||
si->lp_ele = lpPrev(si->lp,si->lp_ele);
|
||||
@@ -1131,7 +1157,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
|
||||
|
||||
/* Get the flags entry. */
|
||||
si->lp_flags = si->lp_ele;
|
||||
int flags = lpGetInteger(si->lp_ele);
|
||||
int64_t flags = lpGetInteger(si->lp_ele);
|
||||
si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek ID. */
|
||||
|
||||
/* Get the ID: it is encoded as difference between the master
|
||||
@@ -1240,7 +1266,7 @@ void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
|
||||
* deleted entries in the listpack header.
|
||||
*
|
||||
* We start flagging: */
|
||||
int flags = lpGetInteger(si->lp_flags);
|
||||
int64_t flags = lpGetInteger(si->lp_flags);
|
||||
flags |= STREAM_ITEM_FLAG_DELETED;
|
||||
lp = lpReplaceInteger(lp,&si->lp_flags,flags);
|
||||
|
||||
@@ -1796,11 +1822,13 @@ void xaddCommand(client *c) {
|
||||
/* Append using the low level function and return the ID. */
|
||||
streamID id;
|
||||
if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
|
||||
&id, parsed_args.id_given ? &parsed_args.id : NULL)
|
||||
== C_ERR)
|
||||
&id, parsed_args.id_given ? &parsed_args.id : NULL) == C_ERR)
|
||||
{
|
||||
addReplyError(c,"The ID specified in XADD is equal or smaller than the "
|
||||
"target stream top item");
|
||||
if (errno == EDOM)
|
||||
addReplyError(c,"The ID specified in XADD is equal or smaller than "
|
||||
"the target stream top item");
|
||||
else
|
||||
addReplyError(c,"Elements are too large to be stored");
|
||||
return;
|
||||
}
|
||||
addReplyStreamID(c,&id);
|
||||
@@ -3233,7 +3261,7 @@ void xtrimCommand(client *c) {
|
||||
|
||||
/* Argument parsing. */
|
||||
streamAddTrimArgs parsed_args;
|
||||
if (streamParseAddOrTrimArgsOrReply(c, &parsed_args, 1) < 0)
|
||||
if (streamParseAddOrTrimArgsOrReply(c, &parsed_args, 0) < 0)
|
||||
return; /* streamParseAddOrTrimArgsOrReply already replied. */
|
||||
|
||||
/* If the key does not exist, we are ok returning zero, that is, the
|
||||
@@ -3562,7 +3590,7 @@ int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) {
|
||||
/* In non-deep mode we just validated the listpack header (encoded size) */
|
||||
if (!deep) return 1;
|
||||
|
||||
next = p = lpFirst(lp);
|
||||
next = p = lpValidateFirst(lp);
|
||||
if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
if (!p) return 0;
|
||||
|
||||
@@ -3600,7 +3628,11 @@ int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) {
|
||||
p = next; if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
|
||||
/* entry id */
|
||||
lpGetIntegerIfValid(p, &valid_record);
|
||||
if (!valid_record) return 0;
|
||||
p = next; if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
lpGetIntegerIfValid(p, &valid_record);
|
||||
if (!valid_record) return 0;
|
||||
p = next; if (!lpValidateNext(lp, &next, size)) return 0;
|
||||
|
||||
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) {
|
||||
|
||||
+17
-1
@@ -797,6 +797,12 @@ void stralgoLCS(client *c) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Detect string truncation or later overflows. */
|
||||
if (sdslen(a) >= UINT32_MAX-1 || sdslen(b) >= UINT32_MAX-1) {
|
||||
addReplyError(c, "String too long for LCS");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Compute the LCS using the vanilla dynamic programming technique of
|
||||
* building a table of LCS(x,y) substrings. */
|
||||
uint32_t alen = sdslen(a);
|
||||
@@ -805,9 +811,19 @@ void stralgoLCS(client *c) {
|
||||
/* Setup an uint32_t array to store at LCS[i,j] the length of the
|
||||
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
|
||||
* we index it as LCS[j+(blen+1)*j] */
|
||||
uint32_t *lcs = zmalloc((alen+1)*(blen+1)*sizeof(uint32_t));
|
||||
#define LCS(A,B) lcs[(B)+((A)*(blen+1))]
|
||||
|
||||
/* Try to allocate the LCS table, and abort on overflow or insufficient memory. */
|
||||
unsigned long long lcssize = (unsigned long long)(alen+1)*(blen+1); /* Can't overflow due to the size limits above. */
|
||||
unsigned long long lcsalloc = lcssize * sizeof(uint32_t);
|
||||
uint32_t *lcs = NULL;
|
||||
if (lcsalloc < SIZE_MAX && lcsalloc / lcssize == sizeof(uint32_t))
|
||||
lcs = ztrymalloc(lcsalloc);
|
||||
if (!lcs) {
|
||||
addReplyError(c, "Insufficient memory");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Start building the LCS table. */
|
||||
for (uint32_t i = 0; i <= alen; i++) {
|
||||
for (uint32_t j = 0; j <= blen; j++) {
|
||||
|
||||
+91
-40
@@ -720,8 +720,8 @@ zskiplistNode *zslLastInLexRange(zskiplist *zsl, zlexrangespec *range) {
|
||||
|
||||
double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
|
||||
char buf[128];
|
||||
if (vlen > sizeof(buf))
|
||||
vlen = sizeof(buf);
|
||||
if (vlen > sizeof(buf) - 1)
|
||||
vlen = sizeof(buf) - 1;
|
||||
memcpy(buf,vstr,vlen);
|
||||
buf[vlen] = '\0';
|
||||
return strtod(buf,NULL);
|
||||
@@ -1242,15 +1242,18 @@ void zsetConvert(robj *zobj, int encoding) {
|
||||
}
|
||||
|
||||
/* Convert the sorted set object into a ziplist if it is not already a ziplist
|
||||
* and if the number of elements and the maximum element size is within the
|
||||
* expected ranges. */
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen) {
|
||||
* and if the number of elements and the maximum element size and total elements size
|
||||
* are within the expected ranges. */
|
||||
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) {
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) return;
|
||||
zset *zset = zobj->ptr;
|
||||
|
||||
if (zset->zsl->length <= server.zset_max_ziplist_entries &&
|
||||
maxelelen <= server.zset_max_ziplist_value)
|
||||
zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
|
||||
maxelelen <= server.zset_max_ziplist_value &&
|
||||
ziplistSafeToAdd(NULL, totelelen))
|
||||
{
|
||||
zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return (by reference) the score of the specified member of the sorted set
|
||||
@@ -1370,20 +1373,28 @@ int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, dou
|
||||
}
|
||||
return 1;
|
||||
} else if (!xx) {
|
||||
/* Optimize: check if the element is too large or the list
|
||||
/* check if the element is too large or the list
|
||||
* becomes too long *before* executing zzlInsert. */
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
|
||||
sdslen(ele) > server.zset_max_ziplist_value)
|
||||
if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries ||
|
||||
sdslen(ele) > server.zset_max_ziplist_value ||
|
||||
!ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
|
||||
{
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (newscore) *newscore = score;
|
||||
*out_flags |= ZADD_OUT_ADDED;
|
||||
return 1;
|
||||
} else {
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
if (newscore) *newscore = score;
|
||||
*out_flags |= ZADD_OUT_ADDED;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
*out_flags |= ZADD_OUT_NOP;
|
||||
return 1;
|
||||
}
|
||||
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
}
|
||||
|
||||
/* Note that the above block handling ziplist would have either returned or
|
||||
* converted the key to skiplist. */
|
||||
if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
zskiplistNode *znode;
|
||||
dictEntry *de;
|
||||
@@ -2361,7 +2372,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat
|
||||
}
|
||||
}
|
||||
|
||||
static int zsetDictGetMaxElementLength(dict *d) {
|
||||
static size_t zsetDictGetMaxElementLength(dict *d, size_t *totallen) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
size_t maxelelen = 0;
|
||||
@@ -2371,6 +2382,8 @@ static int zsetDictGetMaxElementLength(dict *d) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds ele = dictGetKey(de);
|
||||
if (sdslen(ele) > maxelelen) maxelelen = sdslen(ele);
|
||||
if (totallen)
|
||||
(*totallen) += sdslen(ele);
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
@@ -2378,7 +2391,7 @@ static int zsetDictGetMaxElementLength(dict *d) {
|
||||
return maxelelen;
|
||||
}
|
||||
|
||||
static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
|
||||
static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
|
||||
/* DIFF Algorithm 1:
|
||||
*
|
||||
* We perform the diff by iterating all the elements of the first set,
|
||||
@@ -2426,13 +2439,14 @@ static void zdiffAlgorithm1(zsetopsrc *src, long setnum, zset *dstzset, size_t *
|
||||
znode = zslInsert(dstzset->zsl,zval.score,tmp);
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
if (sdslen(tmp) > *maxelelen) *maxelelen = sdslen(tmp);
|
||||
(*totelelen) += sdslen(tmp);
|
||||
}
|
||||
}
|
||||
zuiClearIterator(&src[0]);
|
||||
}
|
||||
|
||||
|
||||
static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
|
||||
static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
|
||||
/* DIFF Algorithm 2:
|
||||
*
|
||||
* Add all the elements of the first set to the auxiliary set.
|
||||
@@ -2486,7 +2500,7 @@ static void zdiffAlgorithm2(zsetopsrc *src, long setnum, zset *dstzset, size_t *
|
||||
|
||||
/* Using this algorithm, we can't calculate the max element as we go,
|
||||
* we have to iterate through all elements to find the max one after. */
|
||||
*maxelelen = zsetDictGetMaxElementLength(dstzset->dict);
|
||||
*maxelelen = zsetDictGetMaxElementLength(dstzset->dict, totelelen);
|
||||
}
|
||||
|
||||
static int zsetChooseDiffAlgorithm(zsetopsrc *src, long setnum) {
|
||||
@@ -2523,14 +2537,14 @@ static int zsetChooseDiffAlgorithm(zsetopsrc *src, long setnum) {
|
||||
return (algo_one_work <= algo_two_work) ? 1 : 2;
|
||||
}
|
||||
|
||||
static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen) {
|
||||
static void zdiff(zsetopsrc *src, long setnum, zset *dstzset, size_t *maxelelen, size_t *totelelen) {
|
||||
/* Skip everything if the smallest input is empty. */
|
||||
if (zuiLength(&src[0]) > 0) {
|
||||
int diff_algo = zsetChooseDiffAlgorithm(src, setnum);
|
||||
if (diff_algo == 1) {
|
||||
zdiffAlgorithm1(src, setnum, dstzset, maxelelen);
|
||||
zdiffAlgorithm1(src, setnum, dstzset, maxelelen, totelelen);
|
||||
} else if (diff_algo == 2) {
|
||||
zdiffAlgorithm2(src, setnum, dstzset, maxelelen);
|
||||
zdiffAlgorithm2(src, setnum, dstzset, maxelelen, totelelen);
|
||||
} else if (diff_algo != 0) {
|
||||
serverPanic("Unknown algorithm");
|
||||
}
|
||||
@@ -2565,7 +2579,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
zsetopsrc *src;
|
||||
zsetopval zval;
|
||||
sds tmp;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
robj *dstobj;
|
||||
zset *dstzset;
|
||||
zskiplistNode *znode;
|
||||
@@ -2701,6 +2715,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
tmp = zuiNewSdsFromValue(&zval);
|
||||
znode = zslInsert(dstzset->zsl,score,tmp);
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
totelelen += sdslen(tmp);
|
||||
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
|
||||
}
|
||||
}
|
||||
@@ -2737,6 +2752,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
/* Remember the longest single element encountered,
|
||||
* to understand if it's possible to convert to ziplist
|
||||
* at the end. */
|
||||
totelelen += sdslen(tmp);
|
||||
if (sdslen(tmp) > maxelelen) maxelelen = sdslen(tmp);
|
||||
/* Update the element with its initial score. */
|
||||
dictSetKey(accumulator, de, tmp);
|
||||
@@ -2771,14 +2787,14 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
dictReleaseIterator(di);
|
||||
dictRelease(accumulator);
|
||||
} else if (op == SET_OP_DIFF) {
|
||||
zdiff(src, setnum, dstzset, &maxelelen);
|
||||
zdiff(src, setnum, dstzset, &maxelelen, &totelelen);
|
||||
} else {
|
||||
serverPanic("Unknown operator");
|
||||
}
|
||||
|
||||
if (dstkey) {
|
||||
if (dstzset->zsl->length) {
|
||||
zsetConvertToZiplistIfNeeded(dstobj, maxelelen);
|
||||
zsetConvertToZiplistIfNeeded(dstobj, maxelelen, totelelen);
|
||||
setKey(c, c->db, dstkey, dstobj);
|
||||
addReplyLongLong(c, zsetLength(dstobj));
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,
|
||||
@@ -2854,7 +2870,7 @@ typedef enum {
|
||||
|
||||
typedef struct zrange_result_handler zrange_result_handler;
|
||||
|
||||
typedef void (*zrangeResultBeginFunction)(zrange_result_handler *c);
|
||||
typedef void (*zrangeResultBeginFunction)(zrange_result_handler *c, long length);
|
||||
typedef void (*zrangeResultFinalizeFunction)(
|
||||
zrange_result_handler *c, size_t result_count);
|
||||
typedef void (*zrangeResultEmitCBufferFunction)(
|
||||
@@ -2882,8 +2898,22 @@ struct zrange_result_handler {
|
||||
zrangeResultEmitLongLongFunction emitResultFromLongLong;
|
||||
};
|
||||
|
||||
/* Result handler methods for responding the ZRANGE to clients. */
|
||||
static void zrangeResultBeginClient(zrange_result_handler *handler) {
|
||||
/* Result handler methods for responding the ZRANGE to clients.
|
||||
* length can be used to provide the result length in advance (avoids deferred reply overhead).
|
||||
* length can be set to -1 if the result length is not know in advance.
|
||||
*/
|
||||
static void zrangeResultBeginClient(zrange_result_handler *handler, long length) {
|
||||
if (length > 0) {
|
||||
/* In case of WITHSCORES, respond with a single array in RESP2, and
|
||||
* nested arrays in RESP3. We can't use a map response type since the
|
||||
* client library needs to know to respect the order. */
|
||||
if (handler->withscores && (handler->client->resp == 2)) {
|
||||
length *= 2;
|
||||
}
|
||||
addReplyArrayLen(handler->client, length);
|
||||
handler->userdata = NULL;
|
||||
return;
|
||||
}
|
||||
handler->userdata = addReplyDeferredLen(handler->client);
|
||||
}
|
||||
|
||||
@@ -2918,6 +2948,9 @@ static void zrangeResultEmitLongLongToClient(zrange_result_handler *handler,
|
||||
static void zrangeResultFinalizeClient(zrange_result_handler *handler,
|
||||
size_t result_count)
|
||||
{
|
||||
/* If the reply size was know at start there's nothing left to do */
|
||||
if (!handler->userdata)
|
||||
return;
|
||||
/* In case of WITHSCORES, respond with a single array in RESP2, and
|
||||
* nested arrays in RESP3. We can't use a map response type since the
|
||||
* client library needs to know to respect the order. */
|
||||
@@ -2929,8 +2962,9 @@ static void zrangeResultFinalizeClient(zrange_result_handler *handler,
|
||||
}
|
||||
|
||||
/* Result handler methods for storing the ZRANGESTORE to a zset. */
|
||||
static void zrangeResultBeginStore(zrange_result_handler *handler)
|
||||
static void zrangeResultBeginStore(zrange_result_handler *handler, long length)
|
||||
{
|
||||
UNUSED(length);
|
||||
handler->dstobj = createZsetZiplistObject();
|
||||
}
|
||||
|
||||
@@ -3025,11 +3059,11 @@ void genericZrangebyrankCommand(zrange_result_handler *handler,
|
||||
if (end < 0) end = llen+end;
|
||||
if (start < 0) start = 0;
|
||||
|
||||
handler->beginResultEmission(handler);
|
||||
|
||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||
* The range is empty when start > end or start >= length. */
|
||||
if (start > end || start >= llen) {
|
||||
handler->beginResultEmission(handler, 0);
|
||||
handler->finalizeResultEmission(handler, 0);
|
||||
return;
|
||||
}
|
||||
@@ -3037,6 +3071,7 @@ void genericZrangebyrankCommand(zrange_result_handler *handler,
|
||||
rangelen = (end-start)+1;
|
||||
result_cardinality = rangelen;
|
||||
|
||||
handler->beginResultEmission(handler, rangelen);
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
@@ -3132,7 +3167,7 @@ void genericZrangebyscoreCommand(zrange_result_handler *handler,
|
||||
client *c = handler->client;
|
||||
unsigned long rangelen = 0;
|
||||
|
||||
handler->beginResultEmission(handler);
|
||||
handler->beginResultEmission(handler, -1);
|
||||
|
||||
/* For invalid offset, return directly. */
|
||||
if (offset > 0 && offset >= (long)zsetLength(zobj)) {
|
||||
@@ -3421,7 +3456,7 @@ void genericZrangebylexCommand(zrange_result_handler *handler,
|
||||
client *c = handler->client;
|
||||
unsigned long rangelen = 0;
|
||||
|
||||
handler->beginResultEmission(handler);
|
||||
handler->beginResultEmission(handler, -1);
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
@@ -3663,7 +3698,12 @@ void zrangeGenericCommand(zrange_result_handler *handler, int argc_start, int st
|
||||
lookupKeyWrite(c->db,key) :
|
||||
lookupKeyRead(c->db,key);
|
||||
if (zobj == NULL) {
|
||||
addReply(c,shared.emptyarray);
|
||||
if (store) {
|
||||
handler->beginResultEmission(handler, -1);
|
||||
handler->finalizeResultEmission(handler, 0);
|
||||
} else {
|
||||
addReply(c, shared.emptyarray);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -3820,11 +3860,16 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
}
|
||||
|
||||
void *arraylen_ptr = addReplyDeferredLen(c);
|
||||
long arraylen = 0;
|
||||
long result_count = 0;
|
||||
|
||||
/* We emit the key only for the blocking variant. */
|
||||
if (emitkey) addReplyBulk(c,key);
|
||||
|
||||
/* Respond with a single (flat) array in RESP2 or if countarg is not
|
||||
* provided (returning a single element). In RESP3, when countarg is
|
||||
* provided, use nested array. */
|
||||
int use_nested_array = c->resp > 2 && countarg != NULL;
|
||||
|
||||
/* Remove the element. */
|
||||
do {
|
||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
@@ -3867,16 +3912,19 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
serverAssertWithInfo(c,zobj,zsetDel(zobj,ele));
|
||||
server.dirty++;
|
||||
|
||||
if (arraylen == 0) { /* Do this only for the first iteration. */
|
||||
if (result_count == 0) { /* Do this only for the first iteration. */
|
||||
char *events[2] = {"zpopmin","zpopmax"};
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,events[where],key,c->db->id);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
}
|
||||
|
||||
if (use_nested_array) {
|
||||
addReplyArrayLen(c,2);
|
||||
}
|
||||
addReplyBulkCBuffer(c,ele,sdslen(ele));
|
||||
addReplyDouble(c,score);
|
||||
sdsfree(ele);
|
||||
arraylen += 2;
|
||||
++result_count;
|
||||
|
||||
/* Remove the key, if indeed needed. */
|
||||
if (zsetLength(zobj) == 0) {
|
||||
@@ -3886,7 +3934,10 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
}
|
||||
} while(--count);
|
||||
|
||||
setDeferredArrayLen(c,arraylen_ptr,arraylen + (emitkey != 0));
|
||||
if (!use_nested_array) {
|
||||
result_count *= 2;
|
||||
}
|
||||
setDeferredArrayLen(c,arraylen_ptr,result_count + (emitkey != 0));
|
||||
}
|
||||
|
||||
/* ZPOPMIN key [<count>] */
|
||||
@@ -3987,7 +4038,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
int uniq = 1;
|
||||
robj *zsetobj;
|
||||
|
||||
if ((zsetobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
|
||||
if ((zsetobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptyarray))
|
||||
== NULL || checkType(c, zsetobj, OBJ_ZSET)) return;
|
||||
size = zsetLength(zsetobj);
|
||||
|
||||
@@ -4023,7 +4074,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c, key, sdslen(key));
|
||||
if (withscores)
|
||||
addReplyDouble(c, dictGetDoubleVal(de));
|
||||
addReplyDouble(c, *(double*)dictGetVal(de));
|
||||
}
|
||||
} else if (zsetobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
ziplistEntry *keys, *vals = NULL;
|
||||
@@ -4172,7 +4223,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ZRANDMEMBER [<count> WITHSCORES] */
|
||||
/* ZRANDMEMBER key [<count> [WITHSCORES]] */
|
||||
void zrandmemberCommand(client *c) {
|
||||
long l;
|
||||
int withscores = 0;
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/pem.h>
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
#include <openssl/decoder.h>
|
||||
#endif
|
||||
|
||||
#define REDIS_TLS_PROTO_TLSv1 (1<<0)
|
||||
#define REDIS_TLS_PROTO_TLSv1_1 (1<<1)
|
||||
@@ -146,12 +149,13 @@ void tlsInit(void) {
|
||||
*/
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
OPENSSL_config(NULL);
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
#elif OPENSSL_VERSION_NUMBER < 0x10101000L
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
|
||||
#else
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_ATFORK, NULL);
|
||||
#endif
|
||||
ERR_load_crypto_strings();
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
|
||||
#ifdef USE_CRYPTO_LOCKS
|
||||
initCryptoLocks();
|
||||
@@ -174,7 +178,8 @@ void tlsCleanup(void) {
|
||||
redis_tls_client_ctx = NULL;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
|
||||
// unavailable on LibreSSL
|
||||
OPENSSL_cleanup();
|
||||
#endif
|
||||
}
|
||||
@@ -320,20 +325,46 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||
if (ctx_config->prefer_server_ciphers)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
|
||||
#if defined(SSL_CTX_set_ecdh_auto)
|
||||
#if ((OPENSSL_VERSION_NUMBER < 0x30000000L) && defined(SSL_CTX_set_ecdh_auto))
|
||||
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||
#endif
|
||||
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
|
||||
|
||||
if (ctx_config->dh_params_file) {
|
||||
FILE *dhfile = fopen(ctx_config->dh_params_file, "r");
|
||||
DH *dh = NULL;
|
||||
if (!dhfile) {
|
||||
serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)
|
||||
EVP_PKEY *pkey = NULL;
|
||||
OSSL_DECODER_CTX *dctx = OSSL_DECODER_CTX_new_for_pkey(
|
||||
&pkey, "PEM", NULL, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL);
|
||||
if (!dctx) {
|
||||
serverLog(LL_WARNING, "No decoder for DH params.");
|
||||
fclose(dhfile);
|
||||
goto error;
|
||||
}
|
||||
if (!OSSL_DECODER_from_fp(dctx, dhfile)) {
|
||||
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
|
||||
OSSL_DECODER_CTX_free(dctx);
|
||||
fclose(dhfile);
|
||||
goto error;
|
||||
}
|
||||
|
||||
OSSL_DECODER_CTX_free(dctx);
|
||||
fclose(dhfile);
|
||||
|
||||
if (SSL_CTX_set0_tmp_dh_pkey(ctx, pkey) <= 0) {
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf);
|
||||
EVP_PKEY_free(pkey);
|
||||
goto error;
|
||||
}
|
||||
/* Not freeing pkey, it is owned by OpenSSL now */
|
||||
#else
|
||||
DH *dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
|
||||
fclose(dhfile);
|
||||
if (!dh) {
|
||||
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
|
||||
@@ -348,6 +379,11 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||
}
|
||||
|
||||
DH_free(dh);
|
||||
#endif
|
||||
} else {
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)
|
||||
SSL_CTX_set_dh_auto(ctx, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* If a client-side certificate is configured, create an explicit client context */
|
||||
|
||||
+36
-11
@@ -326,7 +326,7 @@ void trackingRememberKeyToBroadcast(client *c, char *keyname, size_t keylen) {
|
||||
* tree. This way we know who was the client that did the last
|
||||
* change to the key, and can avoid sending the notification in the
|
||||
* case the client is in NOLOOP mode. */
|
||||
raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
|
||||
raxInsert(bs->keys,(unsigned char*)keyname,keylen,c,NULL);
|
||||
}
|
||||
raxStop(&ri);
|
||||
}
|
||||
@@ -347,13 +347,16 @@ void trackingRememberKeyToBroadcast(client *c, char *keyname, size_t keylen) {
|
||||
* of memory pressure: in that case the key didn't really change, so we want
|
||||
* just to notify the clients that are in the table for this key, that would
|
||||
* otherwise miss the fact we are no longer tracking the key for them. */
|
||||
void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
|
||||
void trackingInvalidateKey(client *c, robj *keyobj, int bcast) {
|
||||
if (TrackingTable == NULL) return;
|
||||
|
||||
if (bcast && raxSize(PrefixTable) > 0)
|
||||
trackingRememberKeyToBroadcast(c,key,keylen);
|
||||
unsigned char *key = (unsigned char*)keyobj->ptr;
|
||||
size_t keylen = sdslen(keyobj->ptr);
|
||||
|
||||
rax *ids = raxFind(TrackingTable,(unsigned char*)key,keylen);
|
||||
if (bcast && raxSize(PrefixTable) > 0)
|
||||
trackingRememberKeyToBroadcast(c,(char *)key,keylen);
|
||||
|
||||
rax *ids = raxFind(TrackingTable,key,keylen);
|
||||
if (ids == raxNotFound) return;
|
||||
|
||||
raxIterator ri;
|
||||
@@ -383,7 +386,15 @@ void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sendTrackingMessage(target,key,keylen,0);
|
||||
/* If target is current client, we need schedule key invalidation.
|
||||
* As the invalidation messages may be interleaved with command
|
||||
* response and should after command response */
|
||||
if (target == server.current_client){
|
||||
incrRefCount(keyobj);
|
||||
listAddNodeTail(server.tracking_pending_keys, keyobj);
|
||||
} else {
|
||||
sendTrackingMessage(target,(char *)keyobj->ptr,sdslen(keyobj->ptr),0);
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
@@ -394,10 +405,22 @@ void trackingInvalidateKeyRaw(client *c, char *key, size_t keylen, int bcast) {
|
||||
raxRemove(TrackingTable,(unsigned char*)key,keylen,NULL);
|
||||
}
|
||||
|
||||
/* Wrapper (the one actually called across the core) to pass the key
|
||||
* as object. */
|
||||
void trackingInvalidateKey(client *c, robj *keyobj) {
|
||||
trackingInvalidateKeyRaw(c,keyobj->ptr,sdslen(keyobj->ptr),1);
|
||||
void trackingHandlePendingKeyInvalidations() {
|
||||
if (!listLength(server.tracking_pending_keys)) return;
|
||||
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.tracking_pending_keys,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
robj *key = listNodeValue(ln);
|
||||
/* current_client maybe freed, so we need to send invalidation
|
||||
* message only when current_client is still alive */
|
||||
if (server.current_client != NULL)
|
||||
sendTrackingMessage(server.current_client,(char *)key->ptr,sdslen(key->ptr),0);
|
||||
decrRefCount(key);
|
||||
}
|
||||
listEmpty(server.tracking_pending_keys);
|
||||
}
|
||||
|
||||
/* This function is called when one or all the Redis databases are
|
||||
@@ -474,7 +497,9 @@ void trackingLimitUsedSlots(void) {
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
raxRandomWalk(&ri,0);
|
||||
if (raxEOF(&ri)) break;
|
||||
trackingInvalidateKeyRaw(NULL,(char*)ri.key,ri.key_len,0);
|
||||
robj *keyobj = createStringObject((char*)ri.key,ri.key_len);
|
||||
trackingInvalidateKey(NULL,keyobj,0);
|
||||
decrRefCount(keyobj);
|
||||
if (raxSize(TrackingTable) <= max_keys) {
|
||||
timeout_counter = 0;
|
||||
raxStop(&ri);
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
#define REDIS_VERSION "255.255.255"
|
||||
#define REDIS_VERSION_NUM 0x00ffffff
|
||||
#define REDIS_VERSION "6.2.7"
|
||||
#define REDIS_VERSION_NUM 0x00060207
|
||||
|
||||
+24
-3
@@ -263,10 +263,21 @@
|
||||
* to stay there to signal that a full scan is needed to get the number of
|
||||
* items inside the ziplist. */
|
||||
#define ZIPLIST_INCR_LENGTH(zl,incr) { \
|
||||
if (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
|
||||
if (intrev16ifbe(ZIPLIST_LENGTH(zl)) < UINT16_MAX) \
|
||||
ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
|
||||
}
|
||||
|
||||
/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
|
||||
* zlbytes*/
|
||||
#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
|
||||
int ziplistSafeToAdd(unsigned char* zl, size_t add) {
|
||||
size_t len = zl? ziplistBlobLen(zl): 0;
|
||||
if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* We use this function to receive information about a ziplist entry.
|
||||
* Note that this is not how the data is actually encoded, is just what we
|
||||
* get filled by a function in order to operate more easily. */
|
||||
@@ -709,7 +720,8 @@ unsigned char *ziplistNew(void) {
|
||||
}
|
||||
|
||||
/* Resize the ziplist. */
|
||||
unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
|
||||
unsigned char *ziplistResize(unsigned char *zl, size_t len) {
|
||||
assert(len < UINT32_MAX);
|
||||
zl = zrealloc(zl,len);
|
||||
ZIPLIST_BYTES(zl) = intrev32ifbe(len);
|
||||
zl[len-1] = ZIP_END;
|
||||
@@ -1070,6 +1082,9 @@ unsigned char *ziplistMerge(unsigned char **first, unsigned char **second) {
|
||||
/* Combined zl length should be limited within UINT16_MAX */
|
||||
zllength = zllength < UINT16_MAX ? zllength : UINT16_MAX;
|
||||
|
||||
/* larger values can't be stored into ZIPLIST_BYTES */
|
||||
assert(zlbytes < UINT32_MAX);
|
||||
|
||||
/* Save offset positions before we start ripping memory apart. */
|
||||
size_t first_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*first));
|
||||
size_t second_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*second));
|
||||
@@ -1145,6 +1160,8 @@ unsigned char *ziplistIndex(unsigned char *zl, int index) {
|
||||
/* No need for "safe" check: when going backwards, we know the header
|
||||
* we're parsing is in the range, we just need to assert (below) that
|
||||
* the size we take doesn't cause p to go outside the allocation. */
|
||||
ZIP_DECODE_PREVLENSIZE(p, prevlensize);
|
||||
assert(p + prevlensize < zl + zlbytes - ZIPLIST_END_SIZE);
|
||||
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
|
||||
while (prevlen > 0 && index--) {
|
||||
p -= prevlen;
|
||||
@@ -1522,8 +1539,12 @@ int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Make sure 'p' really does point to the end of the ziplist. */
|
||||
if (p != zl + bytes - ZIPLIST_END_SIZE)
|
||||
return 0;
|
||||
|
||||
/* Make sure the <zltail> entry really do point to the start of the last entry. */
|
||||
if (prev != ZIPLIST_ENTRY_TAIL(zl))
|
||||
if (prev != NULL && prev != ZIPLIST_ENTRY_TAIL(zl))
|
||||
return 0;
|
||||
|
||||
/* Check that the count in the header is correct */
|
||||
|
||||
@@ -65,6 +65,7 @@ int ziplistValidateIntegrity(unsigned char *zl, size_t size, int deep,
|
||||
void ziplistRandomPair(unsigned char *zl, unsigned long total_count, ziplistEntry *key, ziplistEntry *val);
|
||||
void ziplistRandomPairs(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
|
||||
unsigned int ziplistRandomPairsUnique(unsigned char *zl, unsigned int count, ziplistEntry *keys, ziplistEntry *vals);
|
||||
int ziplistSafeToAdd(unsigned char* zl, size_t add);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int ziplistTest(int argc, char *argv[], int accurate);
|
||||
|
||||
@@ -430,6 +430,9 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* check that the zipmap is not empty. */
|
||||
if (count == 0) return 0;
|
||||
|
||||
/* check that the count in the header is correct */
|
||||
if (zm[0] != ZIPMAP_BIGLEN && zm[0] != count)
|
||||
return 0;
|
||||
|
||||
+2
-2
@@ -430,12 +430,12 @@ size_t zmalloc_get_rss(void) {
|
||||
size_t infolen = sizeof(info);
|
||||
int mib[6];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[1] = KERN_PROC2;
|
||||
mib[2] = KERN_PROC_PID;
|
||||
mib[3] = getpid();
|
||||
mib[4] = sizeof(info);
|
||||
mib[5] = 1;
|
||||
if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0)
|
||||
if (sysctl(mib, __arraycount(mib), &info, &infolen, NULL, 0) == 0)
|
||||
return (size_t)info.p_vm_rssize * getpagesize();
|
||||
|
||||
return 0L;
|
||||
|
||||
Binary file not shown.
@@ -12,6 +12,7 @@ set ::tlsdir "tests/tls"
|
||||
# blocking.
|
||||
proc bg_block_op {host port db ops tls} {
|
||||
set r [redis $host $port 0 $tls]
|
||||
$r client setname LOAD_HANDLER
|
||||
$r select $db
|
||||
|
||||
for {set j 0} {$j < $ops} {incr j} {
|
||||
|
||||
@@ -5,6 +5,7 @@ set ::tlsdir "tests/tls"
|
||||
|
||||
proc bg_complex_data {host port db ops tls} {
|
||||
set r [redis $host $port 0 $tls]
|
||||
$r client setname LOAD_HANDLER
|
||||
$r select $db
|
||||
createComplexDataset $r $ops
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# A fake Redis node for replaying predefined/expected traffic with a client.
|
||||
#
|
||||
# Usage: tclsh fake_redis_node.tcl PORT COMMAND REPLY [ COMMAND REPLY [ ... ] ]
|
||||
#
|
||||
# Commands are given as space-separated strings, e.g. "GET foo", and replies as
|
||||
# RESP-encoded replies minus the trailing \r\n, e.g. "+OK".
|
||||
|
||||
set port [lindex $argv 0];
|
||||
set expected_traffic [lrange $argv 1 end];
|
||||
|
||||
# Reads and parses a command from a socket and returns it as a space-separated
|
||||
# string, e.g. "set foo bar".
|
||||
proc read_command {sock} {
|
||||
set char [read $sock 1]
|
||||
switch $char {
|
||||
* {
|
||||
set numargs [gets $sock]
|
||||
set result {}
|
||||
for {set i 0} {$i<$numargs} {incr i} {
|
||||
read $sock 1; # dollar sign
|
||||
set len [gets $sock]
|
||||
set str [read $sock $len]
|
||||
gets $sock; # trailing \r\n
|
||||
lappend result $str
|
||||
}
|
||||
return $result
|
||||
}
|
||||
{} {
|
||||
# EOF
|
||||
return {}
|
||||
}
|
||||
default {
|
||||
# Non-RESP command
|
||||
set rest [gets $sock]
|
||||
return "$char$rest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc accept {sock host port} {
|
||||
global expected_traffic
|
||||
foreach {expect_cmd reply} $expected_traffic {
|
||||
if {[eof $sock]} {break}
|
||||
set cmd [read_command $sock]
|
||||
if {[string equal -nocase $cmd $expect_cmd]} {
|
||||
puts $sock $reply
|
||||
flush $sock
|
||||
} else {
|
||||
puts $sock "-ERR unexpected command $cmd"
|
||||
break
|
||||
}
|
||||
}
|
||||
close $sock
|
||||
}
|
||||
|
||||
socket -server accept $port
|
||||
after 5000 set done timeout
|
||||
vwait done
|
||||
@@ -5,6 +5,7 @@ set ::tlsdir "tests/tls"
|
||||
proc gen_write_load {host port seconds tls} {
|
||||
set start_time [clock seconds]
|
||||
set r [redis $host $port 1 $tls]
|
||||
$r client setname LOAD_HANDLER
|
||||
$r select 9
|
||||
while 1 {
|
||||
$r set [expr rand()] [expr rand()]
|
||||
|
||||
@@ -158,6 +158,18 @@ tags {"aof"} {
|
||||
assert_match "*not valid*" $result
|
||||
}
|
||||
|
||||
test "Short read: Utility should show the abnormal line num in AOF" {
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof "!!!"
|
||||
}
|
||||
|
||||
catch {
|
||||
exec src/redis-check-aof $aof_path
|
||||
} result
|
||||
assert_match "*ok_up_to_line=8*" $result
|
||||
}
|
||||
|
||||
test "Short read: Utility should be able to fix the AOF" {
|
||||
set result [exec src/redis-check-aof --fix $aof_path << "y\n"]
|
||||
assert_match "*Successfully truncated AOF*" $result
|
||||
|
||||
@@ -33,14 +33,9 @@ start_server {tags {"repl"}} {
|
||||
stop_bg_block_op $load_handle0
|
||||
stop_bg_block_op $load_handle1
|
||||
stop_bg_block_op $load_handle2
|
||||
set retry 10
|
||||
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
|
||||
{
|
||||
after 1000
|
||||
incr retry -1
|
||||
}
|
||||
|
||||
if {[$master debug digest] ne [$slave debug digest]} {
|
||||
wait_for_condition 100 100 {
|
||||
[$master debug digest] == [$slave debug digest]
|
||||
} else {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
set fd [open /tmp/repldump1.txt w]
|
||||
@@ -49,10 +44,8 @@ start_server {tags {"repl"}} {
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
fail "Master - Replica inconsistency, Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ foreach sanitize_dump {no yes} {
|
||||
r debug set-skip-checksum-validation 1
|
||||
set start_time [clock seconds]
|
||||
generate_types
|
||||
set dbsize [r dbsize]
|
||||
r save
|
||||
set cycle 0
|
||||
set stat_terminated_in_restore 0
|
||||
@@ -117,7 +118,7 @@ foreach sanitize_dump {no yes} {
|
||||
set report_and_restart true
|
||||
incr stat_terminated_in_restore
|
||||
write_log_line 0 "corrupt payload: $printable_dump"
|
||||
if {$sanitize_dump == 1} {
|
||||
if {$sanitize_dump == yes} {
|
||||
puts "Server crashed in RESTORE with payload: $printable_dump"
|
||||
}
|
||||
}
|
||||
@@ -133,6 +134,12 @@ foreach sanitize_dump {no yes} {
|
||||
set sent [generate_fuzzy_traffic_on_key "_$k" 1] ;# traffic for 1 second
|
||||
incr stat_traffic_commands_sent [llength $sent]
|
||||
r del "_$k" ;# in case the server terminated, here's where we'll detect it.
|
||||
if {$dbsize != [r dbsize]} {
|
||||
puts "unexpected keys"
|
||||
puts "keys: [r keys *]"
|
||||
puts $sent
|
||||
exit 1
|
||||
}
|
||||
} err ] } {
|
||||
# if the server terminated update stats and restart it
|
||||
set report_and_restart true
|
||||
@@ -140,7 +147,7 @@ foreach sanitize_dump {no yes} {
|
||||
set by_signal [count_log_message 0 "crashed by signal"]
|
||||
incr stat_terminated_by_signal $by_signal
|
||||
|
||||
if {$by_signal != 0 || $sanitize_dump == 1 } {
|
||||
if {$by_signal != 0 || $sanitize_dump == yes} {
|
||||
puts "Server crashed (by signal: $by_signal), with payload: $printable_dump"
|
||||
set print_commands true
|
||||
}
|
||||
@@ -186,7 +193,7 @@ foreach sanitize_dump {no yes} {
|
||||
}
|
||||
}
|
||||
# if we run sanitization we never expect the server to crash at runtime
|
||||
if { $sanitize_dump == 1} {
|
||||
if {$sanitize_dump == yes} {
|
||||
assert_equal $stat_terminated_in_restore 0
|
||||
assert_equal $stat_terminated_in_traffic 0
|
||||
}
|
||||
|
||||
@@ -118,6 +118,16 @@ test {corrupt payload: #3080 - quicklist} {
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: quicklist with empty ziplist} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore key 0 "\x0E\x01\x0B\x0B\x00\x00\x00\x0A\x00\x00\x00\x00\x00\xFF\x09\x00\xC2\x69\x37\x83\x3C\x7F\xFE\x6F" replace} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: #3080 - ziplist} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
# shallow sanitization is enough for restore to safely reject the payload with wrong size
|
||||
@@ -148,6 +158,27 @@ test {corrupt payload: load corrupted rdb with no CRC - #3505} {
|
||||
kill_server $srv ;# let valgrind look for issues
|
||||
}
|
||||
|
||||
foreach sanitize_dump {no yes} {
|
||||
test {corrupt payload: load corrupted rdb with empty keys} {
|
||||
set server_path [tmpdir "server.rdb-corruption-empty-keys-test"]
|
||||
exec cp tests/assets/corrupt_empty_keys.rdb $server_path
|
||||
start_server [list overrides [list "dir" $server_path "dbfilename" "corrupt_empty_keys.rdb" "sanitize-dump-payload" $sanitize_dump]] {
|
||||
r select 0
|
||||
assert_equal [r dbsize] 0
|
||||
|
||||
verify_log_message 0 "*skipping empty key: set*" 0
|
||||
verify_log_message 0 "*skipping empty key: list_quicklist*" 0
|
||||
verify_log_message 0 "*skipping empty key: list_quicklist_empty_ziplist*" 0
|
||||
verify_log_message 0 "*skipping empty key: list_ziplist*" 0
|
||||
verify_log_message 0 "*skipping empty key: hash*" 0
|
||||
verify_log_message 0 "*skipping empty key: hash_ziplist*" 0
|
||||
verify_log_message 0 "*skipping empty key: zset*" 0
|
||||
verify_log_message 0 "*skipping empty key: zset_ziplist*" 0
|
||||
verify_log_message 0 "*empty keys skipped: 8*" 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: listpack invalid size header} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
@@ -224,6 +255,16 @@ test {corrupt payload: hash dupliacte records} {
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: hash empty zipmap} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch { r RESTORE _hash 0 "\x09\x02\x00\xFF\x09\x00\xC0\xF1\xB8\x67\x4C\x16\xAC\xE3" } err
|
||||
assert_match "*Bad data format*" $err
|
||||
verify_log_message 0 "*Zipmap integrity check failed*" 0
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - NPD in streamIteratorGetID} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
@@ -328,12 +369,13 @@ test {corrupt payload: fuzzer findings - leak in rdbloading due to dup entry in
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - empty intset div by zero} {
|
||||
test {corrupt payload: fuzzer findings - empty intset} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
r debug set-skip-checksum-validation 1
|
||||
r RESTORE _setbig 0 "\x02\xC0\xC0\x06\x02\x5F\x39\xC0\x02\x02\x5F\x33\xC0\x00\x02\x5F\x31\xC0\x04\xC0\x08\x02\x5F\x37\x02\x5F\x35\x09\x00\xC5\xD4\x6D\xBA\xAD\x14\xB7\xE7"
|
||||
catch {r SRANDMEMBER _setbig }
|
||||
catch {r RESTORE _setbig 0 "\x02\xC0\xC0\x06\x02\x5F\x39\xC0\x02\x02\x5F\x33\xC0\x00\x02\x5F\x31\xC0\x04\xC0\x08\x02\x5F\x37\x02\x5F\x35\x09\x00\xC5\xD4\x6D\xBA\xAD\x14\xB7\xE7"} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,14 +549,13 @@ test {corrupt payload: fuzzer findings - valgrind invalid read} {
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - HRANDFIELD on bad ziplist} {
|
||||
test {corrupt payload: fuzzer findings - empty hash ziplist} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
r RESTORE _int 0 "\x04\xC0\x01\x09\x00\xF6\x8A\xB6\x7A\x85\x87\x72\x4D"
|
||||
catch {r HRANDFIELD _int}
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
catch {r RESTORE _int 0 "\x04\xC0\x01\x09\x00\xF6\x8A\xB6\x7A\x85\x87\x72\x4D"} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,5 +570,144 @@ test {corrupt payload: fuzzer findings - stream with no records} {
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - quicklist ziplist tail followed by extra data which start with 0xff} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {
|
||||
r restore key 0 "\x0E\x01\x11\x11\x00\x00\x00\x0A\x00\x00\x00\x01\x00\x00\xF6\xFF\xB0\x6C\x9C\xFF\x09\x00\x9C\x37\x47\x49\x4D\xDE\x94\xF5" replace
|
||||
} err
|
||||
assert_match "*Bad data format*" $err
|
||||
verify_log_message 0 "*integrity check failed*" 0
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - dict init to huge size} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore key 0 "\x02\x81\xC0\x00\x02\x5F\x31\xC0\x02\x09\x00\xB2\x1B\xE5\x17\x2E\x15\xF4\x6C" replace} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - huge string} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore key 0 "\x00\x81\x01\x09\x00\xF6\x2B\xB6\x7A\x85\x87\x72\x4D"} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - stream PEL without consumer} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore _stream 0 "\x0F\x01\x10\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x3B\x40\x42\x19\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x20\x10\x00\x00\x20\x01\x00\x01\x20\x03\x02\x05\x01\x03\x20\x05\x40\x00\x04\x82\x5F\x31\x03\x05\x60\x19\x80\x32\x02\x05\x01\xFF\x02\x81\x00\x00\x01\x7B\x08\xF0\xB2\x34\x02\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x7B\x08\xF0\xB2\x34\x01\x01\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x01\x35\xB2\xF0\x08\x7B\x01\x00\x00\x01\x01\x13\x41\x6C\x69\x63\x65\x35\xB2\xF0\x08\x7B\x01\x00\x00\x01\x00\x00\x01\x7B\x08\xF0\xB2\x34\x00\x00\x00\x00\x00\x00\x00\x01\x09\x00\x28\x2F\xE0\xC5\x04\xBB\xA7\x31"} err
|
||||
assert_match "*Bad data format*" $err
|
||||
#catch {r XINFO STREAM _stream FULL }
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - stream listpack valgrind issue} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
r debug set-skip-checksum-validation 1
|
||||
r restore _stream 0 "\x0F\x01\x10\x00\x00\x01\x7B\x09\x5E\x94\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x40\x42\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x01\x02\x01\x00\x01\x00\x01\x01\x01\x00\x01\x05\x01\x03\x01\x25\x01\x00\x01\x01\x01\x82\x5F\x31\x03\x05\x01\x02\x01\x32\x01\x00\x01\x01\x01\x02\x01\xF0\x01\xFF\x02\x81\x00\x00\x01\x7B\x09\x5E\x95\x31\x00\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x7B\x09\x5E\x95\x24\x00\x01\x00\x00\x01\x7B\x09\x5E\x95\x24\x00\x00\x00\x00\x00\x00\x00\x00\x5C\x95\x5E\x09\x7B\x01\x00\x00\x01\x01\x05\x41\x6C\x69\x63\x65\x4B\x95\x5E\x09\x7B\x01\x00\x00\x01\x00\x00\x01\x7B\x09\x5E\x95\x24\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x19\x29\x94\xDF\x76\xF8\x1A\xC6"
|
||||
catch {r XINFO STREAM _stream FULL }
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - stream with bad lpFirst} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore _stream 0 "\x0F\x01\x10\x00\x00\x01\x7B\x0E\x52\xD2\xEC\x00\x00\x00\x00\x00\x00\x00\x00\x40\x42\x42\x00\x00\x00\x18\x00\x02\xF7\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x01\x02\x01\x00\x01\x00\x01\x01\x01\x00\x01\x05\x01\x03\x01\x01\x01\x00\x01\x01\x01\x82\x5F\x31\x03\x05\x01\x02\x01\x01\x01\x01\x01\x01\x01\x02\x01\x05\x01\xFF\x02\x81\x00\x00\x01\x7B\x0E\x52\xD2\xED\x01\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x7B\x0E\x52\xD2\xED\x00\x01\x00\x00\x01\x7B\x0E\x52\xD2\xED\x00\x00\x00\x00\x00\x00\x00\x00\xED\xD2\x52\x0E\x7B\x01\x00\x00\x01\x01\x05\x41\x6C\x69\x63\x65\xED\xD2\x52\x0E\x7B\x01\x00\x00\x01\x00\x00\x01\x7B\x0E\x52\xD2\xED\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\xAC\x05\xC9\x97\x5D\x45\x80\xB3"} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - stream listpack lpPrev valgrind issue} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload no
|
||||
r debug set-skip-checksum-validation 1
|
||||
r restore _stream 0 "\x0F\x01\x10\x00\x00\x01\x7B\x0E\xAE\x66\x36\x00\x00\x00\x00\x00\x00\x00\x00\x40\x42\x42\x00\x00\x00\x18\x00\x02\x01\x01\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x00\x01\x02\x01\x00\x01\x00\x01\x01\x01\x00\x01\x1D\x01\x03\x01\x24\x01\x00\x01\x01\x69\x82\x5F\x31\x03\x05\x01\x02\x01\x33\x01\x00\x01\x01\x01\x02\x01\x05\x01\xFF\x02\x81\x00\x00\x01\x7B\x0E\xAE\x66\x69\x00\x01\x07\x6D\x79\x67\x72\x6F\x75\x70\x81\x00\x00\x01\x7B\x0E\xAE\x66\x5A\x00\x01\x00\x00\x01\x7B\x0E\xAE\x66\x5A\x00\x00\x00\x00\x00\x00\x00\x00\x94\x66\xAE\x0E\x7B\x01\x00\x00\x01\x01\x05\x41\x6C\x69\x63\x65\x83\x66\xAE\x0E\x7B\x01\x00\x00\x01\x00\x00\x01\x7B\x0E\xAE\x66\x5A\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\xD5\xD7\xA5\x5C\x63\x1C\x09\x40"
|
||||
catch {r XREVRANGE _stream 1618622681 606195012389}
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - stream with non-integer entry id} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore _streambig 0 "\x0F\x03\x10\x00\x00\x01\x7B\x13\x34\xC3\xB2\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4F\x40\x5C\x18\x5C\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x80\x20\x01\x00\x01\x20\x03\x00\x05\x20\x1C\x40\x09\x05\x01\x01\x82\x5F\x31\x03\x80\x0D\x00\x02\x20\x0D\x00\x02\xA0\x19\x00\x03\x20\x0B\x02\x82\x5F\x33\xA0\x19\x00\x04\x20\x0D\x00\x04\x20\x19\x00\xFF\x10\x00\x00\x01\x7B\x13\x34\xC3\xB2\x00\x00\x00\x00\x00\x00\x00\x05\xC3\x40\x56\x40\x61\x18\x61\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x06\x01\x01\x82\x5F\x35\x03\x05\x20\x1E\x40\x0B\x03\x01\x01\x06\x01\x40\x0B\x03\x01\x01\xDF\xFB\x20\x05\x02\x82\x5F\x37\x60\x1A\x20\x0E\x00\xFC\x20\x05\x00\x08\xC0\x1B\x00\xFD\x20\x0C\x02\x82\x5F\x39\x20\x1B\x00\xFF\x10\x00\x00\x01\x7B\x13\x34\xC3\xB3\x00\x00\x00\x00\x00\x00\x00\x03\xC3\x3D\x40\x4A\x18\x4A\x00\x00\x00\x15\x00\x02\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x40\x00\x00\x05\x60\x07\x02\xDF\xFD\x02\xC0\x23\x09\x01\x01\x86\x75\x6E\x69\x71\x75\x65\x07\xA0\x2D\x02\x08\x01\xFF\x0C\x81\x00\x00\x01\x7B\x13\x34\xC3\xB4\x00\x00\x09\x00\x9D\xBD\xD5\xB9\x33\xC4\xC5\xFF"} err
|
||||
#catch {r XINFO STREAM _streambig FULL }
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - empty quicklist} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {
|
||||
r restore key 0 "\x0E\xC0\x2B\x15\x00\x00\x00\x0A\x00\x00\x00\x01\x00\x00\xE0\x62\x58\xEA\xDF\x22\x00\x00\x00\xFF\x09\x00\xDF\x35\xD2\x67\xDC\x0E\x89\xAB" replace
|
||||
} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - empty zset} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore key 0 "\x05\xC0\x01\x09\x00\xF6\x8A\xB6\x7A\x85\x87\x72\x4D"} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - hash with len of 0} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r config set sanitize-dump-payload yes
|
||||
r debug set-skip-checksum-validation 1
|
||||
catch {r restore key 0 "\x04\xC0\x21\x09\x00\xF6\x8A\xB6\x7A\x85\x87\x72\x4D"} err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - stream double free listpack when insert dup node to rax returns 0} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r debug set-skip-checksum-validation 1
|
||||
r config set sanitize-dump-payload yes
|
||||
catch { r restore _stream 0 "\x0F\x03\x10\x00\x00\x01\x7B\x60\x5A\x23\x79\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x40\x4F\x40\x5C\x18\x5C\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x00\x01\x20\x03\x00\x05\x20\x1C\x40\x09\x05\x01\x01\x82\x5F\x31\x03\x80\x0D\x00\x02\x20\x0D\x00\x02\xA0\x19\x00\x03\x20\x0B\x02\x82\x5F\x33\xA0\x19\x00\x04\x20\x0D\x00\x04\x20\x19\x00\xFF\x10\x00\x00\x01\x7B\x60\x5A\x23\x79\x00\x00\x00\x00\x00\x00\x00\x05\xC3\x40\x51\x40\x5E\x18\x5E\x00\x00\x00\x24\x00\x05\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x06\x01\x01\x82\x5F\x35\x03\x05\x20\x1E\x40\x0B\x03\x01\x01\x06\x01\x80\x0B\x00\x02\x20\x0B\x02\x82\x5F\x37\xA0\x19\x00\x03\x20\x0D\x00\x08\xA0\x19\x00\x04\x20\x0B\x02\x82\x5F\x39\x20\x19\x00\xFF\x10\x00\x00\x01\x7B\x60\x5A\x23\x79\x00\x00\x00\x00\x00\x00\x00\x00\xC3\x3B\x40\x49\x18\x49\x00\x00\x00\x15\x00\x02\x01\x00\x01\x02\x01\x84\x69\x74\x65\x6D\x05\x85\x76\x61\x6C\x75\x65\x06\x40\x10\x00\x00\x20\x01\x40\x00\x00\x05\x20\x07\x40\x09\xC0\x22\x09\x01\x01\x86\x75\x6E\x69\x71\x75\x65\x07\xA0\x2C\x02\x08\x01\xFF\x0C\x81\x00\x00\x01\x7B\x60\x5A\x23\x7A\x01\x00\x09\x00\x9C\x8F\x1E\xBF\x2E\x05\x59\x09" replace } err
|
||||
assert_match "*Bad data format*" $err
|
||||
r ping
|
||||
}
|
||||
}
|
||||
|
||||
test {corrupt payload: fuzzer findings - invalid access in ziplist tail prevlen decoding} {
|
||||
start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] {
|
||||
r debug set-skip-checksum-validation 1
|
||||
r config set sanitize-dump-payload no
|
||||
r restore _listbig 0 "\x0e\x02\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\x02\x5F\x39\x04\xF9\x02\x02\x5F\x37\x04\xF7\x02\x02\x5F\x35\xFF\x19\x19\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\xF5\x02\x02\x5F\x33\x04\xF3\x02\x02\x5F\x31\xFE\xF1\xFF\x09\x00\x64\x0C\xEB\x03\xDF\x36\x61\xCE"
|
||||
catch { r RPOPLPUSH _listbig _listbig }
|
||||
assert_equal [count_log_message 0 "crashed by signal"] 0
|
||||
assert_equal [count_log_message 0 "ASSERTION FAILED"] 1
|
||||
}
|
||||
}
|
||||
|
||||
} ;# tags
|
||||
|
||||
|
||||
@@ -83,7 +83,11 @@ start_server {} {
|
||||
} else {
|
||||
fail "Failover from node 0 to node 1 did not finish"
|
||||
}
|
||||
|
||||
# stop the write load and make sure no more commands processed
|
||||
stop_write_load $load_handler
|
||||
wait_load_handlers_disconnected
|
||||
|
||||
$node_2 replicaof $node_1_host $node_1_port
|
||||
wait_for_sync $node_0
|
||||
wait_for_sync $node_2
|
||||
|
||||
@@ -45,7 +45,7 @@ start_server [list overrides [list "dir" $server_path] keep_persistence true] {
|
||||
test {Test RDB stream encoding} {
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
if {rand() < 0.9} {
|
||||
r xadd stream * foo $j
|
||||
r xadd stream * foo abc
|
||||
} else {
|
||||
r xadd stream * bar $j
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ start_server {tags {"cli"}} {
|
||||
set _ $tmp
|
||||
}
|
||||
|
||||
proc _run_cli {opts args} {
|
||||
set cmd [rediscli [srv host] [srv port] [list -n 9 {*}$args]]
|
||||
proc _run_cli {host port db opts args} {
|
||||
set cmd [rediscli $host $port [list -n $db {*}$args]]
|
||||
foreach {key value} $opts {
|
||||
if {$key eq "pipe"} {
|
||||
set cmd "sh -c \"$value | $cmd\""
|
||||
@@ -84,15 +84,19 @@ start_server {tags {"cli"}} {
|
||||
}
|
||||
|
||||
proc run_cli {args} {
|
||||
_run_cli {} {*}$args
|
||||
_run_cli [srv host] [srv port] 9 {} {*}$args
|
||||
}
|
||||
|
||||
proc run_cli_with_input_pipe {cmd args} {
|
||||
_run_cli [list pipe $cmd] -x {*}$args
|
||||
_run_cli [srv host] [srv port] 9 [list pipe $cmd] -x {*}$args
|
||||
}
|
||||
|
||||
proc run_cli_with_input_file {path args} {
|
||||
_run_cli [list path $path] -x {*}$args
|
||||
_run_cli [srv host] [srv port] 9 [list path $path] -x {*}$args
|
||||
}
|
||||
|
||||
proc run_cli_host_port_db {host port db args} {
|
||||
_run_cli $host $port $db {} {*}$args
|
||||
}
|
||||
|
||||
proc test_nontty_cli {name code} {
|
||||
@@ -207,6 +211,30 @@ start_server {tags {"cli"}} {
|
||||
assert_equal "foo\nbar" [run_cli lrange list 0 -1]
|
||||
}
|
||||
|
||||
if {!$::tls} { ;# fake_redis_node doesn't support TLS
|
||||
test_nontty_cli "ASK redirect test" {
|
||||
# Set up two fake Redis nodes.
|
||||
set tclsh [info nameofexecutable]
|
||||
set script "tests/helpers/fake_redis_node.tcl"
|
||||
set port1 [find_available_port $::baseport $::portcount]
|
||||
set port2 [find_available_port $::baseport $::portcount]
|
||||
set p1 [exec $tclsh $script $port1 \
|
||||
"SET foo bar" "-ASK 12182 127.0.0.1:$port2" &]
|
||||
set p2 [exec $tclsh $script $port2 \
|
||||
"ASKING" "+OK" \
|
||||
"SET foo bar" "+OK" &]
|
||||
# Make sure both fake nodes have started listening
|
||||
wait_for_condition 50 50 {
|
||||
[catch {close [socket "127.0.0.1" $port1]}] == 0 && \
|
||||
[catch {close [socket "127.0.0.1" $port2]}] == 0
|
||||
} else {
|
||||
fail "Failed to start fake Redis nodes"
|
||||
}
|
||||
# Run the cli
|
||||
assert_equal "OK" [run_cli_host_port_db "127.0.0.1" $port1 0 -c SET foo bar]
|
||||
}
|
||||
}
|
||||
|
||||
test_nontty_cli "Quoted input arguments" {
|
||||
r set "\x00\x00" "value"
|
||||
assert_equal "value" [run_cli --quoted-input get {"\x00\x00"}]
|
||||
@@ -215,7 +243,6 @@ start_server {tags {"cli"}} {
|
||||
test_nontty_cli "No accidental unquoting of input arguments" {
|
||||
run_cli --quoted-input set {"\x41\x41"} quoted-val
|
||||
run_cli set {"\x41\x41"} unquoted-val
|
||||
|
||||
assert_equal "quoted-val" [r get AA]
|
||||
assert_equal "unquoted-val" [r get {"\x41\x41"}]
|
||||
}
|
||||
@@ -283,9 +310,9 @@ start_server {tags {"cli"}} {
|
||||
assert_equal {key:2} [run_cli --scan --quoted-pattern {"*:\x32"}]
|
||||
}
|
||||
|
||||
test "Connecting as a replica" {
|
||||
proc test_redis_cli_repl {} {
|
||||
set fd [open_cli "--replica"]
|
||||
wait_for_condition 500 500 {
|
||||
wait_for_condition 500 100 {
|
||||
[string match {*slave0:*state=online*} [r info]]
|
||||
} else {
|
||||
fail "redis-cli --replica did not connect"
|
||||
@@ -294,14 +321,30 @@ start_server {tags {"cli"}} {
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
r set test-key test-value-$i
|
||||
}
|
||||
r client kill type slave
|
||||
catch {
|
||||
assert_match {*SET*key-a*} [read_cli $fd]
|
||||
|
||||
wait_for_condition 500 100 {
|
||||
[string match {*test-value-99*} [read_cli $fd]]
|
||||
} else {
|
||||
fail "redis-cli --replica didn't read commands"
|
||||
}
|
||||
|
||||
close_cli $fd
|
||||
fconfigure $fd -blocking true
|
||||
r client kill type slave
|
||||
catch { close_cli $fd } err
|
||||
assert_match {*Server closed the connection*} $err
|
||||
}
|
||||
|
||||
test "Connecting as a replica" {
|
||||
# Disk-based master
|
||||
assert_match "OK" [r config set repl-diskless-sync no]
|
||||
test_redis_cli_repl
|
||||
|
||||
# Disk-less master
|
||||
assert_match "OK" [r config set repl-diskless-sync yes]
|
||||
assert_match "OK" [r config set repl-diskless-sync-delay 0]
|
||||
test_redis_cli_repl
|
||||
} {}
|
||||
|
||||
test "Piping raw protocol" {
|
||||
set cmds [tmpfile "cli_cmds"]
|
||||
set cmds_fd [open $cmds "w"]
|
||||
|
||||
@@ -21,15 +21,9 @@ start_server {tags {"repl network"}} {
|
||||
stop_bg_complex_data $load_handle0
|
||||
stop_bg_complex_data $load_handle1
|
||||
stop_bg_complex_data $load_handle2
|
||||
set retry 10
|
||||
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
|
||||
{
|
||||
after 1000
|
||||
incr retry -1
|
||||
}
|
||||
assert {[$master dbsize] > 0}
|
||||
|
||||
if {[$master debug digest] ne [$slave debug digest]} {
|
||||
wait_for_condition 100 100 {
|
||||
[$master debug digest] == [$slave debug digest]
|
||||
} else {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
set fd [open /tmp/repldump1.txt w]
|
||||
@@ -38,10 +32,9 @@ start_server {tags {"repl network"}} {
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
fail "Master - Replica inconsistency, Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
assert {[$master dbsize] > 0}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,28 +58,41 @@ start_server {tags {"repl"}} {
|
||||
test {With min-slaves-to-write (1,3): master should be writable} {
|
||||
$master config set min-slaves-max-lag 3
|
||||
$master config set min-slaves-to-write 1
|
||||
$master set foo bar
|
||||
} {OK}
|
||||
assert_equal OK [$master set foo 123]
|
||||
assert_equal OK [$master eval "return redis.call('set','foo',12345)" 0]
|
||||
}
|
||||
|
||||
test {With min-slaves-to-write (2,3): master should not be writable} {
|
||||
$master config set min-slaves-max-lag 3
|
||||
$master config set min-slaves-to-write 2
|
||||
catch {$master set foo bar} e
|
||||
set e
|
||||
} {NOREPLICAS*}
|
||||
assert_error "*NOREPLICAS*" {$master set foo bar}
|
||||
assert_error "*NOREPLICAS*" {$master eval "redis.call('set','foo','bar')" 0}
|
||||
}
|
||||
|
||||
test {With not enough good slaves, read in Lua script is still accepted} {
|
||||
$master config set min-slaves-max-lag 3
|
||||
$master config set min-slaves-to-write 1
|
||||
$master eval "redis.call('set','foo','bar')" 0
|
||||
|
||||
$master config set min-slaves-to-write 2
|
||||
$master eval "return redis.call('get','foo')" 0
|
||||
} {bar}
|
||||
|
||||
test {With min-slaves-to-write: master not writable with lagged slave} {
|
||||
$master config set min-slaves-max-lag 2
|
||||
$master config set min-slaves-to-write 1
|
||||
assert {[$master set foo bar] eq {OK}}
|
||||
assert_equal OK [$master set foo 123]
|
||||
assert_equal OK [$master eval "return redis.call('set','foo',12345)" 0]
|
||||
# Killing a slave to make it become a lagged slave.
|
||||
exec kill -SIGSTOP [srv 0 pid]
|
||||
# Waiting for slave kill.
|
||||
wait_for_condition 100 100 {
|
||||
[catch {$master set foo bar}] != 0
|
||||
[catch {$master set foo 123}] != 0
|
||||
} else {
|
||||
fail "Master didn't become readonly"
|
||||
}
|
||||
catch {$master set foo bar} err
|
||||
assert_match {NOREPLICAS*} $err
|
||||
assert_error "*NOREPLICAS*" {$master set foo 123}
|
||||
assert_error "*NOREPLICAS*" {$master eval "return redis.call('set','foo',12345)" 0}
|
||||
exec kill -SIGCONT [srv 0 pid]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,15 +97,9 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond mdl sdl reco
|
||||
fail "Slave still not connected after some time"
|
||||
}
|
||||
|
||||
set retry 10
|
||||
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
|
||||
{
|
||||
after 1000
|
||||
incr retry -1
|
||||
}
|
||||
assert {[$master dbsize] > 0}
|
||||
|
||||
if {[$master debug digest] ne [$slave debug digest]} {
|
||||
wait_for_condition 100 100 {
|
||||
[$master debug digest] == [$slave debug digest]
|
||||
} else {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
set fd [open /tmp/repldump1.txt w]
|
||||
@@ -114,10 +108,9 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond mdl sdl reco
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Replica inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
fail "Master - Replica inconsistency, Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
assert {[$master dbsize] > 0}
|
||||
eval $cond
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,15 +316,12 @@ foreach mdl {no yes} {
|
||||
stop_write_load $load_handle3
|
||||
stop_write_load $load_handle4
|
||||
|
||||
# Make sure that slaves and master have same
|
||||
# number of keys
|
||||
wait_for_condition 500 100 {
|
||||
[$master dbsize] == [[lindex $slaves 0] dbsize] &&
|
||||
[$master dbsize] == [[lindex $slaves 1] dbsize] &&
|
||||
[$master dbsize] == [[lindex $slaves 2] dbsize]
|
||||
} else {
|
||||
fail "Different number of keys between master and replica after too long time."
|
||||
}
|
||||
# Make sure no more commands processed
|
||||
wait_load_handlers_disconnected
|
||||
|
||||
wait_for_ofs_sync $master [lindex $slaves 0]
|
||||
wait_for_ofs_sync $master [lindex $slaves 1]
|
||||
wait_for_ofs_sync $master [lindex $slaves 2]
|
||||
|
||||
# Check digests
|
||||
set digest [$master debug digest]
|
||||
@@ -774,6 +771,45 @@ test "diskless replication child being killed is collected" {
|
||||
}
|
||||
}
|
||||
|
||||
test "diskless replication read pipe cleanup" {
|
||||
# In diskless replication, we create a read pipe for the RDB, between the child and the parent.
|
||||
# When we close this pipe (fd), the read handler also needs to be removed from the event loop (if it still registered).
|
||||
# Otherwise, next time we will use the same fd, the registration will be fail (panic), because
|
||||
# we will use EPOLL_CTL_MOD (the fd still register in the event loop), on fd that already removed from epoll_ctl
|
||||
start_server {tags {"repl"}} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set master_pid [srv 0 pid]
|
||||
$master config set repl-diskless-sync yes
|
||||
$master config set repl-diskless-sync-delay 0
|
||||
|
||||
# put enough data in the db, and slowdown the save, to keep the parent busy at the read process
|
||||
$master config set rdb-key-save-delay 100000
|
||||
$master debug populate 20000 test 10000
|
||||
$master config set rdbcompression no
|
||||
start_server {} {
|
||||
set replica [srv 0 client]
|
||||
set loglines [count_log_lines 0]
|
||||
$replica config set repl-diskless-load swapdb
|
||||
$replica replicaof $master_host $master_port
|
||||
|
||||
# wait for the replicas to start reading the rdb
|
||||
wait_for_log_messages 0 {"*Loading DB in memory*"} $loglines 800 10
|
||||
|
||||
set loglines [count_log_lines 0]
|
||||
# send FLUSHALL so the RDB child will be killed
|
||||
$master flushall
|
||||
|
||||
# wait for another RDB child process to be started
|
||||
wait_for_log_messages -1 {"*Background RDB transfer started by pid*"} $loglines 800 10
|
||||
|
||||
# make sure master is alive
|
||||
$master ping
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {replicaof right after disconnection} {
|
||||
# this is a rare race condition that was reproduced sporadically by the psync2 unit.
|
||||
# see details in #7205
|
||||
@@ -864,7 +900,7 @@ test {Kill rdb child process if its dumping RDB is not useful} {
|
||||
# Slave2 disconnect with master
|
||||
$slave2 slaveof no one
|
||||
# Should kill child
|
||||
wait_for_condition 20 10 {
|
||||
wait_for_condition 100 10 {
|
||||
[s 0 rdb_bgsave_in_progress] eq 0
|
||||
} else {
|
||||
fail "can't kill rdb child"
|
||||
|
||||
@@ -16,8 +16,16 @@ ifneq ($(uname_S),FreeBSD)
|
||||
LIBS = -lc
|
||||
endif
|
||||
|
||||
# OS X 11.x doesn't have /usr/lib/libSystem.dylib and needs an explicit setting.
|
||||
ifeq ($(uname_S),Darwin)
|
||||
ifeq ("$(wildcard /usr/lib/libSystem.dylib)","")
|
||||
LIBS = -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lsystem
|
||||
endif
|
||||
endif
|
||||
|
||||
TEST_MODULES = \
|
||||
commandfilter.so \
|
||||
basics.so \
|
||||
testrdb.so \
|
||||
fork.so \
|
||||
infotest.so \
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include "redismodule.h"
|
||||
#include <string.h>
|
||||
|
||||
/* --------------------------------- Helpers -------------------------------- */
|
||||
@@ -152,11 +152,64 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return failTest(ctx, "Could not verify key to be unlinked");
|
||||
}
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
/* TEST.STRING.TRUNCATE -- Test truncating an existing string object. */
|
||||
int TestStringTruncate(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_AutoMemory(ctx);
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModule_Call(ctx, "SET", "cc", "foo", "abcde");
|
||||
RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "foo"), REDISMODULE_READ | REDISMODULE_WRITE);
|
||||
if (!k) return failTest(ctx, "Could not create key");
|
||||
|
||||
size_t len = 0;
|
||||
char* s;
|
||||
|
||||
/* expand from 5 to 8 and check null pad */
|
||||
if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 8)) {
|
||||
return failTest(ctx, "Could not truncate string value (8)");
|
||||
}
|
||||
s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
|
||||
if (!s) {
|
||||
return failTest(ctx, "Failed to read truncated string (8)");
|
||||
} else if (len != 8) {
|
||||
return failTest(ctx, "Failed to expand string value (8)");
|
||||
} else if (0 != strncmp(s, "abcde\0\0\0", 8)) {
|
||||
return failTest(ctx, "Failed to null pad string value (8)");
|
||||
}
|
||||
|
||||
/* shrink from 8 to 4 */
|
||||
if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 4)) {
|
||||
return failTest(ctx, "Could not truncate string value (4)");
|
||||
}
|
||||
s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
|
||||
if (!s) {
|
||||
return failTest(ctx, "Failed to read truncated string (4)");
|
||||
} else if (len != 4) {
|
||||
return failTest(ctx, "Failed to shrink string value (4)");
|
||||
} else if (0 != strncmp(s, "abcd", 4)) {
|
||||
return failTest(ctx, "Failed to truncate string value (4)");
|
||||
}
|
||||
|
||||
/* shrink to 0 */
|
||||
if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 0)) {
|
||||
return failTest(ctx, "Could not truncate string value (0)");
|
||||
}
|
||||
s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
|
||||
if (!s) {
|
||||
return failTest(ctx, "Failed to read truncated string (0)");
|
||||
} else if (len != 0) {
|
||||
return failTest(ctx, "Failed to shrink string value to (0)");
|
||||
}
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
|
||||
RedisModuleString *key) {
|
||||
RedisModule_AutoMemory(ctx);
|
||||
/* Increment a counter on the notifications: for each key notified we
|
||||
* increment a counter */
|
||||
RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type,
|
||||
@@ -168,6 +221,7 @@ int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
|
||||
|
||||
/* TEST.NOTIFICATIONS -- Test Keyspace Notifications. */
|
||||
int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_AutoMemory(ctx);
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
@@ -279,6 +333,9 @@ int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_AOF)) FAIL("AOF Flag not set after config set");
|
||||
|
||||
/* Disable RDB saving and test the flag. */
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "save", "");
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (flags & REDISMODULE_CTX_FLAGS_RDB) FAIL("RDB Flag was set");
|
||||
/* Enable RDB to test RDB flags */
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "save", "900 1");
|
||||
@@ -290,8 +347,12 @@ int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (flags & REDISMODULE_CTX_FLAGS_READONLY) FAIL("Read-only flag was set");
|
||||
if (flags & REDISMODULE_CTX_FLAGS_CLUSTER) FAIL("Cluster flag was set");
|
||||
|
||||
/* Disable maxmemory and test the flag. (it is implicitly set in 32bit builds. */
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0");
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (flags & REDISMODULE_CTX_FLAGS_MAXMEMORY) FAIL("Maxmemory flag was set");
|
||||
|
||||
/* Enable maxmemory and test the flag. */
|
||||
RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "100000000");
|
||||
flags = RedisModule_GetContextFlags(ctx);
|
||||
if (!(flags & REDISMODULE_CTX_FLAGS_MAXMEMORY))
|
||||
@@ -324,7 +385,11 @@ end:
|
||||
int TestAssertStringReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) {
|
||||
RedisModuleString *mystr, *expected;
|
||||
|
||||
if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
|
||||
if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) {
|
||||
RedisModule_Log(ctx,"warning","Test error reply: %s",
|
||||
RedisModule_CallReplyStringPtr(reply, NULL));
|
||||
return 0;
|
||||
} else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
|
||||
RedisModule_Log(ctx,"warning","Unexpected reply type %d",
|
||||
RedisModule_CallReplyType(reply));
|
||||
return 0;
|
||||
@@ -345,7 +410,11 @@ int TestAssertStringReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char
|
||||
/* Return 1 if the reply matches the specified integer, otherwise log errors
|
||||
* in the server log and return 0. */
|
||||
int TestAssertIntegerReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, long long expected) {
|
||||
if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) {
|
||||
if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) {
|
||||
RedisModule_Log(ctx,"warning","Test error reply: %s",
|
||||
RedisModule_CallReplyStringPtr(reply, NULL));
|
||||
return 0;
|
||||
} else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) {
|
||||
RedisModule_Log(ctx,"warning","Unexpected reply type %d",
|
||||
RedisModule_CallReplyType(reply));
|
||||
return 0;
|
||||
@@ -366,8 +435,11 @@ int TestAssertIntegerReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, lon
|
||||
reply = RedisModule_Call(ctx,name,__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/* TEST.IT -- Run all the tests. */
|
||||
int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
/* TEST.BASICS -- Run all the tests.
|
||||
* Note: it is useful to run these tests from the module rather than TCL
|
||||
* since it's easier to check the reply types like that (make a distinction
|
||||
* between 0 and "0", etc. */
|
||||
int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
@@ -390,6 +462,9 @@ int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
T("test.string.append","");
|
||||
if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail;
|
||||
|
||||
T("test.string.truncate","");
|
||||
if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
|
||||
|
||||
T("test.unlink","");
|
||||
if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
|
||||
|
||||
@@ -407,7 +482,7 @@ int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
|
||||
fail:
|
||||
RedisModule_ReplyWithSimpleString(ctx,
|
||||
"SOME TEST NOT PASSED! Check server logs");
|
||||
"SOME TEST DID NOT PASS! Check server logs");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -430,6 +505,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
TestStringAppendAM,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.string.truncate",
|
||||
TestStringTruncate,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.string.printf",
|
||||
TestStringPrintf,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
@@ -442,8 +521,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.it",
|
||||
TestIt,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"test.basics",
|
||||
TestBasics,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_SubscribeToKeyspaceEvents(ctx,
|
||||
@@ -31,6 +31,11 @@ void HelloBlock_FreeData(RedisModuleCtx *ctx, void *privdata) {
|
||||
RedisModule_Free(privdata);
|
||||
}
|
||||
|
||||
/* Private data freeing callback for BLOCK.BLOCK command. */
|
||||
void HelloBlock_FreeStringData(RedisModuleCtx *ctx, void *privdata) {
|
||||
RedisModule_FreeString(ctx, (RedisModuleString*)privdata);
|
||||
}
|
||||
|
||||
/* The thread entry point that actually executes the blocking part
|
||||
* of the command BLOCK.DEBUG. */
|
||||
void *BlockDebug_ThreadMain(void *arg) {
|
||||
@@ -195,6 +200,66 @@ int HelloDoubleBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleBlockedClient *blocked_client = NULL;
|
||||
|
||||
/* BLOCK.BLOCK [TIMEOUT] -- Blocks the current client until released
|
||||
* or TIMEOUT seconds. If TIMEOUT is zero, no timeout function is
|
||||
* registered.
|
||||
*/
|
||||
int Block_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (RedisModule_IsBlockedReplyRequest(ctx)) {
|
||||
RedisModuleString *r = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
return RedisModule_ReplyWithString(ctx, r);
|
||||
} else if (RedisModule_IsBlockedTimeoutRequest(ctx)) {
|
||||
RedisModule_UnblockClient(blocked_client, NULL); /* Must be called to avoid leaks. */
|
||||
blocked_client = NULL;
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Timed out");
|
||||
}
|
||||
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
long long timeout;
|
||||
|
||||
if (RedisModule_StringToLongLong(argv[1], &timeout) != REDISMODULE_OK) {
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid timeout");
|
||||
}
|
||||
if (blocked_client) {
|
||||
return RedisModule_ReplyWithError(ctx, "ERR another client already blocked");
|
||||
}
|
||||
|
||||
/* Block client. We use this function as both a reply and optional timeout
|
||||
* callback and differentiate the different code flows above.
|
||||
*/
|
||||
blocked_client = RedisModule_BlockClient(ctx, Block_RedisCommand,
|
||||
timeout > 0 ? Block_RedisCommand : NULL, HelloBlock_FreeStringData, timeout);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* BLOCK.IS_BLOCKED -- Returns 1 if we have a blocked client, or 0 otherwise.
|
||||
*/
|
||||
int IsBlocked_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
UNUSED(argv);
|
||||
UNUSED(argc);
|
||||
RedisModule_ReplyWithLongLong(ctx, blocked_client ? 1 : 0);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* BLOCK.RELEASE [reply] -- Releases the blocked client and produce the specified reply.
|
||||
*/
|
||||
int Release_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
if (!blocked_client) {
|
||||
return RedisModule_ReplyWithError(ctx, "ERR No blocked client");
|
||||
}
|
||||
|
||||
RedisModuleString *replystr = argv[1];
|
||||
RedisModule_RetainString(ctx, replystr);
|
||||
RedisModule_UnblockClient(blocked_client, replystr);
|
||||
blocked_client = NULL;
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
UNUSED(argv);
|
||||
@@ -215,5 +280,17 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
HelloBlockNoTracking_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "block.block",
|
||||
Block_RedisCommand, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"block.is_blocked",
|
||||
IsBlocked_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"block.release",
|
||||
Release_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -470,19 +470,19 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (fsltype == NULL)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpoppush",fsl_bpoppush,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpoppush",fsl_bpoppush,"",1,2,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.getall",fsl_getall,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.getall",fsl_getall,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "blockonkeys.popall", blockonkeys_popall,
|
||||
|
||||
+54
-19
@@ -34,13 +34,14 @@ array set ::redis::fd {}
|
||||
array set ::redis::addr {}
|
||||
array set ::redis::blocking {}
|
||||
array set ::redis::deferred {}
|
||||
array set ::redis::readraw {}
|
||||
array set ::redis::reconnect {}
|
||||
array set ::redis::tls {}
|
||||
array set ::redis::callback {}
|
||||
array set ::redis::state {} ;# State in non-blocking reply reading
|
||||
array set ::redis::statestack {} ;# Stack of states, for nested mbulks
|
||||
|
||||
proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}}} {
|
||||
proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}} {readraw 0}} {
|
||||
if {$tls} {
|
||||
package require tls
|
||||
::tls::init \
|
||||
@@ -58,6 +59,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}}} {
|
||||
set ::redis::addr($id) [list $server $port]
|
||||
set ::redis::blocking($id) 1
|
||||
set ::redis::deferred($id) $defer
|
||||
set ::redis::readraw($id) $readraw
|
||||
set ::redis::reconnect($id) 0
|
||||
set ::redis::tls($id) $tls
|
||||
::redis::redis_reset_state $id
|
||||
@@ -158,6 +160,7 @@ proc ::redis::__method__close {id fd} {
|
||||
catch {unset ::redis::addr($id)}
|
||||
catch {unset ::redis::blocking($id)}
|
||||
catch {unset ::redis::deferred($id)}
|
||||
catch {unset ::redis::readraw($id)}
|
||||
catch {unset ::redis::reconnect($id)}
|
||||
catch {unset ::redis::tls($id)}
|
||||
catch {unset ::redis::state($id)}
|
||||
@@ -174,6 +177,10 @@ proc ::redis::__method__deferred {id fd val} {
|
||||
set ::redis::deferred($id) $val
|
||||
}
|
||||
|
||||
proc ::redis::__method__readraw {id fd val} {
|
||||
set ::redis::readraw($id) $val
|
||||
}
|
||||
|
||||
proc ::redis::redis_write {fd buf} {
|
||||
puts -nonewline $fd $buf
|
||||
}
|
||||
@@ -240,26 +247,53 @@ proc ::redis::redis_read_null fd {
|
||||
return {}
|
||||
}
|
||||
|
||||
proc ::redis::redis_read_bool fd {
|
||||
set v [redis_read_line $fd]
|
||||
if {$v == "t"} {return 1}
|
||||
if {$v == "f"} {return 0}
|
||||
return -code error "Bad protocol, '$v' as bool type"
|
||||
}
|
||||
|
||||
proc ::redis::redis_read_verbatim_str fd {
|
||||
set v [redis_bulk_read $fd]
|
||||
# strip the first 4 chars ("txt:")
|
||||
return [string range $v 4 end]
|
||||
}
|
||||
|
||||
proc ::redis::redis_read_reply {id fd} {
|
||||
set type [read $fd 1]
|
||||
switch -exact -- $type {
|
||||
_ {redis_read_null $fd}
|
||||
: -
|
||||
+ {redis_read_line $fd}
|
||||
, {expr {double([redis_read_line $fd])}}
|
||||
- {return -code error [redis_read_line $fd]}
|
||||
$ {redis_bulk_read $fd}
|
||||
> -
|
||||
~ -
|
||||
* {redis_multi_bulk_read $id $fd}
|
||||
% {redis_read_map $id $fd}
|
||||
default {
|
||||
if {$type eq {}} {
|
||||
catch {close $fd}
|
||||
set ::redis::fd($id) {}
|
||||
return -code error "I/O error reading reply"
|
||||
if {$::redis::readraw($id)} {
|
||||
return [redis_read_line $fd]
|
||||
}
|
||||
|
||||
while {1} {
|
||||
set type [read $fd 1]
|
||||
switch -exact -- $type {
|
||||
_ {return [redis_read_null $fd]}
|
||||
: -
|
||||
( -
|
||||
+ {return [redis_read_line $fd]}
|
||||
, {return [expr {double([redis_read_line $fd])}]}
|
||||
# {return [redis_read_bool $fd]}
|
||||
= {return [redis_read_verbatim_str $fd]}
|
||||
- {return -code error [redis_read_line $fd]}
|
||||
$ {return [redis_bulk_read $fd]}
|
||||
> -
|
||||
~ -
|
||||
* {return [redis_multi_bulk_read $id $fd]}
|
||||
% {return [redis_read_map $id $fd]}
|
||||
| {
|
||||
# ignore attributes for now (nowhere to store them)
|
||||
redis_read_map $id $fd
|
||||
continue
|
||||
}
|
||||
default {
|
||||
if {$type eq {}} {
|
||||
catch {close $fd}
|
||||
set ::redis::fd($id) {}
|
||||
return -code error "I/O error reading reply"
|
||||
}
|
||||
return -code error "Bad protocol, '$type' as reply type byte"
|
||||
}
|
||||
return -code error "Bad protocol, '$type' as reply type byte"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,6 +324,7 @@ proc ::redis::redis_readable {fd id} {
|
||||
: -
|
||||
+ {redis_call_callback $id reply [string range $line 1 end-1]}
|
||||
- {redis_call_callback $id err [string range $line 1 end-1]}
|
||||
( {redis_call_callback $id reply [string range $line 1 end-1]}
|
||||
$ {
|
||||
dict set ::redis::state($id) bulk \
|
||||
[expr [string range $line 1 end-1]+2]
|
||||
|
||||
@@ -160,12 +160,13 @@ proc server_is_up {host port retrynum} {
|
||||
# case err_return names a return variable for the message to be logged.
|
||||
proc tags_acceptable {err_return} {
|
||||
upvar $err_return err
|
||||
set tags $::tags
|
||||
|
||||
# If tags are whitelisted, make sure there's match
|
||||
if {[llength $::allowtags] > 0} {
|
||||
set matched 0
|
||||
foreach tag $::allowtags {
|
||||
if {[lsearch $::tags $tag] >= 0} {
|
||||
if {[lsearch $tags $tag] >= 0} {
|
||||
incr matched
|
||||
}
|
||||
}
|
||||
@@ -176,12 +177,27 @@ proc tags_acceptable {err_return} {
|
||||
}
|
||||
|
||||
foreach tag $::denytags {
|
||||
if {[lsearch $::tags $tag] >= 0} {
|
||||
if {[lsearch $tags $tag] >= 0} {
|
||||
set err "Tag: $tag denied"
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
if {$::external && [lsearch $tags "external:skip"] >= 0} {
|
||||
set err "Not supported on external server"
|
||||
return 0
|
||||
}
|
||||
|
||||
if {$::singledb && [lsearch $tags "singledb:skip"] >= 0} {
|
||||
set err "Not supported on singledb"
|
||||
return 0
|
||||
}
|
||||
|
||||
if {$::tls && [lsearch $tags "tls:skip"] >= 0} {
|
||||
set err "Not supported in tls mode"
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -320,7 +336,9 @@ proc start_server {options {code undefined}} {
|
||||
dict set srv "port" $::port
|
||||
set client [redis $::host $::port 0 $::tls]
|
||||
dict set srv "client" $client
|
||||
$client select 9
|
||||
if {!$::singledb} {
|
||||
$client select 9
|
||||
}
|
||||
|
||||
set config {}
|
||||
dict set config "port" $::port
|
||||
|
||||
+28
-2
@@ -504,6 +504,14 @@ proc stop_write_load {handle} {
|
||||
catch {exec /bin/kill -9 $handle}
|
||||
}
|
||||
|
||||
proc wait_load_handlers_disconnected {{level 0}} {
|
||||
wait_for_condition 50 100 {
|
||||
![string match {*name=LOAD_HANDLER*} [r $level client list]]
|
||||
} else {
|
||||
fail "load_handler(s) still connected after too long time."
|
||||
}
|
||||
}
|
||||
|
||||
proc K { x y } { set x }
|
||||
|
||||
# Shuffle a list with Fisher-Yates algorithm.
|
||||
@@ -600,6 +608,7 @@ proc generate_fuzzy_traffic_on_key {key duration} {
|
||||
set arity [lindex $cmd_info 1]
|
||||
set arity [expr $arity < 0 ? - $arity: $arity]
|
||||
set firstkey [lindex $cmd_info 3]
|
||||
set lastkey [lindex $cmd_info 4]
|
||||
set i 1
|
||||
if {$cmd == "XINFO"} {
|
||||
lappend cmd "STREAM"
|
||||
@@ -629,7 +638,7 @@ proc generate_fuzzy_traffic_on_key {key duration} {
|
||||
incr i 4
|
||||
}
|
||||
for {} {$i < $arity} {incr i} {
|
||||
if {$i == $firstkey} {
|
||||
if {$i == $firstkey || $i == $lastkey} {
|
||||
lappend cmd $key
|
||||
} else {
|
||||
lappend cmd [randomValue]
|
||||
@@ -761,4 +770,21 @@ proc psubscribe {client channels} {
|
||||
proc punsubscribe {client {channels {}}} {
|
||||
$client punsubscribe {*}$channels
|
||||
consume_subscribe_messages $client punsubscribe $channels
|
||||
}
|
||||
}
|
||||
|
||||
proc wait_for_blocked_client {} {
|
||||
wait_for_condition 50 100 {
|
||||
[s blocked_clients] ne 0
|
||||
} else {
|
||||
fail "no blocked clients"
|
||||
}
|
||||
}
|
||||
|
||||
proc wait_for_blocked_clients_count {count {maxtries 100} {delay 10}} {
|
||||
wait_for_condition $maxtries $delay {
|
||||
[s blocked_clients] == $count
|
||||
} else {
|
||||
fail "Timeout waiting for blocked clients"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-5
@@ -70,12 +70,14 @@ set ::all_tests {
|
||||
unit/hyperloglog
|
||||
unit/lazyfree
|
||||
unit/wait
|
||||
unit/pause
|
||||
unit/pendingquerybuf
|
||||
unit/tls
|
||||
unit/tracking
|
||||
unit/oom-score-adj
|
||||
unit/shutdown
|
||||
unit/networking
|
||||
unit/cluster
|
||||
}
|
||||
# Index to the next test to run in the ::all_tests list.
|
||||
set ::next_test 0
|
||||
@@ -114,6 +116,7 @@ set ::stop_on_failure 0
|
||||
set ::dump_logs 0
|
||||
set ::loop 0
|
||||
set ::tlsdir "tests/tls"
|
||||
set ::singledb 0
|
||||
|
||||
# Set to 1 when we are running in client mode. The Redis test uses a
|
||||
# server-client model to run tests simultaneously. The server instance
|
||||
@@ -191,7 +194,7 @@ proc reconnect {args} {
|
||||
dict set srv "client" $client
|
||||
|
||||
# select the right db when we don't have to authenticate
|
||||
if {![dict exists $config "requirepass"]} {
|
||||
if {![dict exists $config "requirepass"] && !$::singledb} {
|
||||
$client select 9
|
||||
}
|
||||
|
||||
@@ -210,8 +213,14 @@ proc redis_deferring_client {args} {
|
||||
set client [redis [srv $level "host"] [srv $level "port"] 1 $::tls]
|
||||
|
||||
# select the right db and read the response (OK)
|
||||
$client select 9
|
||||
$client read
|
||||
if {!$::singledb} {
|
||||
$client select 9
|
||||
$client read
|
||||
} else {
|
||||
# For timing/symmetry with the above select
|
||||
$client ping
|
||||
$client read
|
||||
}
|
||||
return $client
|
||||
}
|
||||
|
||||
@@ -225,8 +234,13 @@ proc redis_client {args} {
|
||||
# create client that defers reading reply
|
||||
set client [redis [srv $level "host"] [srv $level "port"] 0 $::tls]
|
||||
|
||||
# select the right db and read the response (OK)
|
||||
$client select 9
|
||||
# select the right db and read the response (OK), or at least ping
|
||||
# the server if we're in a singledb mode.
|
||||
if {$::singledb} {
|
||||
$client ping
|
||||
} else {
|
||||
$client select 9
|
||||
}
|
||||
return $client
|
||||
}
|
||||
|
||||
@@ -563,6 +577,7 @@ proc print_help_screen {} {
|
||||
"--port <port> TCP port to use against external host."
|
||||
"--baseport <port> Initial port number for spawned redis servers."
|
||||
"--portcount <num> Port range for spawned redis servers."
|
||||
"--singledb Use a single database, avoid SELECT."
|
||||
"--help Print this help screen."
|
||||
} "\n"]
|
||||
}
|
||||
@@ -669,6 +684,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--timeout}} {
|
||||
set ::timeout $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--singledb}} {
|
||||
set ::singledb 1
|
||||
} elseif {$opt eq {--help}} {
|
||||
print_help_screen
|
||||
exit 0
|
||||
|
||||
@@ -41,15 +41,8 @@ start_server {tags {"aofrw"}} {
|
||||
stop_write_load $load_handle3
|
||||
stop_write_load $load_handle4
|
||||
|
||||
# Make sure that we remain the only connected client.
|
||||
# This step is needed to make sure there are no pending writes
|
||||
# that will be processed between the two "debug digest" calls.
|
||||
wait_for_condition 50 100 {
|
||||
[llength [split [string trim [r client list]] "\n"]] == 1
|
||||
} else {
|
||||
puts [r client list]
|
||||
fail "Clients generating loads are not disconnecting"
|
||||
}
|
||||
# Make sure no more commands processed, before taking debug digest
|
||||
wait_load_handlers_disconnected
|
||||
|
||||
# Get the data set digest
|
||||
set d1 [r debug digest]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user