Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a767d84a72 | ||
|
|
ee91248505 | ||
|
|
3cf3beff2c | ||
|
|
54eb66495f | ||
|
|
77f91e09cf | ||
|
|
d60953bf25 | ||
|
|
89c68ba3f7 | ||
|
|
fcda82930e | ||
|
|
cefd33925c | ||
|
|
10202ba1fd | ||
|
|
97816fd63e | ||
|
|
3b792f5100 | ||
|
|
da2906e507 | ||
|
|
18264d641b | ||
|
|
918c9aa58c | ||
|
|
8cc6698567 | ||
|
|
e9c9e4c2af | ||
|
|
ee4696b150 | ||
|
|
c9e370c6b8 | ||
|
|
a3ca53e4a7 | ||
|
|
7a62eb96ef | ||
|
|
0efb93d0c0 | ||
|
|
ce878b6ed5 | ||
|
|
1fc8ef81aa | ||
|
|
a5e24eabc3 | ||
|
|
1f7d08b76d | ||
|
|
2bea502d25 | ||
|
|
b5931405ff | ||
|
|
fd4413002d | ||
|
|
77ff332b4c | ||
|
|
4f0f799c96 | ||
|
|
0c1273c389 | ||
|
|
708a4e8a9b | ||
|
|
eac4115d36 | ||
|
|
d075df1768 | ||
|
|
80a49c37f9 | ||
|
|
60870d3a10 | ||
|
|
d90f599b4d | ||
|
|
8ee3bddfc7 | ||
|
|
4780fe78ba | ||
|
|
f3b77510ef | ||
|
|
7f3fcedb8c | ||
|
|
f93b2fa524 | ||
|
|
89682d96ea | ||
|
|
920e108f84 | ||
|
|
7569b210d6 | ||
|
|
3c610b4e8d | ||
|
|
16b2d07f0a | ||
|
|
19f3358564 | ||
|
|
4891612bb5 | ||
|
|
4d2a31aed8 | ||
|
|
9f63fc98df | ||
|
|
1a9e70c1d7 | ||
|
|
69b1b5be6b | ||
|
|
1fd97ee7f1 | ||
|
|
1a9855d7d9 | ||
|
|
32a6e3e48e | ||
|
|
c9e6cda9e8 | ||
|
|
d3eeacf93d | ||
|
|
da5dc4583f | ||
|
|
0fefed25e4 | ||
|
|
e9fbc96033 | ||
|
|
08ec8f71ca | ||
|
|
09e1fe274d | ||
|
|
09f9e4b030 | ||
|
|
8d8d68ddf3 | ||
|
|
7a7fbe708a | ||
|
|
b5830486ca | ||
|
|
b7a2a53a8c | ||
|
|
278bd6e3b6 | ||
|
|
61d9a1542a | ||
|
|
0e7ea0aaa3 | ||
|
|
3b38164e8f | ||
|
|
ef9fe9b0cb | ||
|
|
8066d2a197 | ||
|
|
d3f4dec440 | ||
|
|
33f42665a8 | ||
|
|
68d71d8370 | ||
|
|
62588dbfa7 | ||
|
|
bb78454b0f | ||
|
|
7a41047a61 | ||
|
|
4eb3028bc6 | ||
|
|
5d09f9bc8e | ||
|
|
2810de9fc1 | ||
|
|
f5c63ce0ae | ||
|
|
7f289c3b92 | ||
|
|
7ab62d4b92 | ||
|
|
495dd0da54 | ||
|
|
c1ccf0f188 | ||
|
|
a50dad73c2 | ||
|
|
d6294d05cd | ||
|
|
8c56fc864b | ||
|
|
98b1314f07 | ||
|
|
08b03e2322 | ||
|
|
239069dec7 | ||
|
|
90bf631345 | ||
|
|
2f8a07498e | ||
|
|
436ed56d45 | ||
|
|
680f89fbf9 | ||
|
|
388efbf8b6 | ||
|
|
0ccbdcee26 | ||
|
|
707a59c643 |
+467
@@ -11,6 +11,473 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.10 Released Mon Oct 26 09:21:49 IST 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency: SECURITY if you use an affected platform (see below).
|
||||
Otherwise the upgrade urgency is MODERATE.
|
||||
|
||||
This release fixes a potential heap overflow when using a heap allocator other
|
||||
than jemalloc or glibc's malloc. See:
|
||||
https://github.com/redis/redis/pull/7963
|
||||
|
||||
Other fixes in this release:
|
||||
|
||||
* Avoid case of Lua scripts being consistently aborted due to OOM
|
||||
* XPENDING will not update consumer's seen-time
|
||||
* A blocked XREADGROUP didn't propagated the XSETID to replicas / AOF
|
||||
* UNLINK support for streams
|
||||
* RESTORE ABSTTL won't store expired keys into the DB
|
||||
* Hide AUTH from MONITOR
|
||||
* Cluster: reduce spurious PFAIL/FAIL states upon delayed PONG receival
|
||||
* Cluster: Fix case of clusters mixing accidentally by gossip
|
||||
* Cluster: Allow blocked XREAD on a cluster replica
|
||||
* Cluster: Optimize memory usage CLUSTER SLOTS command
|
||||
* RedisModule_ValueLength support for stream data type
|
||||
* Minor fixes in redis-check-rdb and redis-cli
|
||||
* Fix redis-check-rdb support for modules aux data
|
||||
* Add fsync in replica when full RDB payload was received
|
||||
|
||||
Full list of commits:
|
||||
|
||||
Yossi Gottlieb in commit ce0d74d8f:
|
||||
Fix wrong zmalloc_size() assumption. (#7963)
|
||||
1 file changed, 3 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 066699240:
|
||||
Backport Lua 5.2.2 stack overflow fix. (#7733)
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
WuYunlong in commit 8a90c7ef3:
|
||||
Add fsync to readSyncBulkPayload(). (#7839)
|
||||
1 file changed, 11 insertions(+)
|
||||
|
||||
Ariel Shtul in commit f0df2bb3c:
|
||||
Fix redis-check-rdb support for modules aux data (#7826)
|
||||
3 files changed, 21 insertions(+), 1 deletion(-)
|
||||
|
||||
hwware in commit 7add2a412:
|
||||
fix memory leak in sentinel connection sharing
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Oran Agra in commit 315e648f8:
|
||||
Allow blocked XREAD on a cluster replica (#7881)
|
||||
3 files changed, 100 insertions(+), 2 deletions(-)
|
||||
|
||||
guybe7 in commit 4967ee94e:
|
||||
Modules: Invalidate saved_oparray after use (#7688)
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
antirez in commit 065003e8f:
|
||||
Modules: remove spurious call from moduleHandleBlockedClients().
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
Angus Pearson in commit 6cdf62928:
|
||||
Fix broken interval and repeat bahaviour in redis-cli (incluing cluster mode)
|
||||
1 file changed, 11 insertions(+), 6 deletions(-)
|
||||
|
||||
antirez in commit cb6a4971c:
|
||||
Cluster: introduce data_received field.
|
||||
2 files changed, 27 insertions(+), 10 deletions(-)
|
||||
|
||||
Madelyn Olson in commit 83f4de865:
|
||||
Hide AUTH from monitor
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 3ba08d185:
|
||||
Support streams in general module API functions
|
||||
3 files changed, 11 insertions(+), 1 deletion(-)
|
||||
|
||||
Itamar Haber in commit 109c0635c:
|
||||
Expands lazyfree's effort estimate to include Streams (#5794)
|
||||
1 file changed, 24 insertions(+)
|
||||
|
||||
huangzhw in commit 235210d5b:
|
||||
defrag.c activeDefragSdsListAndDict when defrag sdsele, We can't use (#7492)
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Oran Agra in commit fdd3162fe:
|
||||
RESTORE ABSTTL skip expired keys - leak (#7511)
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Oran Agra in commit 6139d6d18:
|
||||
RESTORE ABSTTL won't store expired keys into the db (#7472)
|
||||
4 files changed, 45 insertions(+), 15 deletions(-)
|
||||
|
||||
Liu Zhen in commit 0f502c58d:
|
||||
fix clusters mixing accidentally by gossip
|
||||
1 file changed, 10 insertions(+), 2 deletions(-)
|
||||
|
||||
Guy Benoish in commit 37fd50718:
|
||||
XPENDING should not update consumer's seen-time
|
||||
4 files changed, 30 insertions(+), 18 deletions(-)
|
||||
|
||||
antirez in commit a3ca53e4a:
|
||||
Also use propagate() in streamPropagateGroupID().
|
||||
1 file changed, 11 insertions(+), 1 deletion(-)
|
||||
|
||||
yanhui13 in commit 7a62eb96e:
|
||||
optimize the output of cluster slots
|
||||
1 file changed, 7 insertions(+), 4 deletions(-)
|
||||
|
||||
srzhao in commit 0efb93d0c:
|
||||
Check OOM at script start to get stable lua OOM state.
|
||||
3 files changed, 11 insertions(+), 4 deletions(-)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.9 Released Thu Apr 17 12:41:00 CET 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency:CRITICAL if you use Streams with AOF ore replicas.
|
||||
Otherwise the upgrade urgency is LOW.
|
||||
|
||||
This release has a speed improvement and a critical fix:
|
||||
|
||||
* FIX: XREADGROUP when fetching data in a blocking way, would not
|
||||
emit the XCLAIM in the AOF file and to replicas. This means
|
||||
that the last ID is not updated, and that restarting the server
|
||||
will have the effect of reprocessing some entries.
|
||||
* NEW: Clients blocked on the same key are now unblocked on
|
||||
O(1) time. Backported from Redis 6.
|
||||
|
||||
Commits:
|
||||
|
||||
1fc8ef81a Fix XCLAIM propagation in AOF/replicas for blocking XREADGROUP.
|
||||
a5e24eabc Speedup: unblock clients on keys in O(1).
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.8 Released Thu Mar 12 16:05:41 CET 2020
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency HIGH: This release fixes security issues.
|
||||
|
||||
This is a list of fixes in this release:
|
||||
|
||||
Salvatore Sanfilippo in commit 2bea502d:
|
||||
Merge pull request #6975 from dustinmm80/add-arm-latomic-linking
|
||||
Dustin Collins in commit b5931405:
|
||||
Fix Pi building needing -latomic, backport
|
||||
1 file changed, 9 insertions(+)
|
||||
|
||||
srzhao in commit fd441300:
|
||||
fix impl of aof-child whitelist SIGUSR1 feature.
|
||||
1 file changed, 5 insertions(+), 4 deletions(-)
|
||||
|
||||
Ariel in commit 77ff332b:
|
||||
fix ThreadSafeContext lock/unlock function names
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Guy Benoish in commit 4f0f799c:
|
||||
XREADGROUP should propagate XCALIM/SETID in MULTI/EXEC
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Oran Agra in commit 0c1273c3:
|
||||
Fix client flags to be int64 in module.c
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
Guy Benoish in commit 708a4e8a:
|
||||
Fix small bugs related to replica and monitor ambiguity
|
||||
2 files changed, 8 insertions(+), 6 deletions(-)
|
||||
|
||||
WuYunlong in commit eac4115d:
|
||||
Fix lua related memory leak.
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit d075df17:
|
||||
Simplify #6379 changes.
|
||||
2 files changed, 4 insertions(+), 9 deletions(-)
|
||||
|
||||
WuYunlong in commit 80a49c37:
|
||||
Free allocated sds in pfdebugCommand() to avoid memory leak.
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit 60870d3a:
|
||||
Jump to right label on AOF parsing error.
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit d90f599b:
|
||||
Free fakeclient argv on AOF error.
|
||||
1 file changed, 11 insertions(+), 3 deletions(-)
|
||||
|
||||
WuYunlong in commit 8ee3bddf:
|
||||
Fix potential memory leak of rioWriteBulkStreamID().
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
WuYunlong in commit 4780fe78:
|
||||
Fix potential memory leak of clusterLoadConfig().
|
||||
1 file changed, 20 insertions(+), 5 deletions(-)
|
||||
|
||||
Leo Murillo in commit f3b77510:
|
||||
Fix bug on KEYS command where pattern starts with * followed by \x00 (null char).
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 7f3fcedb:
|
||||
Blocking XREAD[GROUP] should always reply with valid data (or timeout)
|
||||
3 files changed, 44 insertions(+), 10 deletions(-)
|
||||
|
||||
antirez in commit f93b2fa5:
|
||||
XCLAIM: Create the consumer only on successful claims.
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
Guy Benoish in commit 89682d96:
|
||||
Stream: Handle streamID-related edge cases
|
||||
4 files changed, 54 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit 920e108f:
|
||||
Fix ip and missing mode in RM_GetClusterNodeInfo().
|
||||
1 file changed, 5 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 7569b210:
|
||||
Inline protocol: handle empty strings well.
|
||||
1 file changed, 2 insertions(+), 6 deletions(-)
|
||||
|
||||
Khem Raj in commit 3c610b4e:
|
||||
Mark extern definition of SDS_NOINIT in sds.h
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Seunghoon Woo in commit 16b2d07f:
|
||||
[FIX] revisit CVE-2015-8080 vulnerability
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
yz1509 in commit 19f33585:
|
||||
avoid sentinel changes promoted_slave to be its own replica.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.7 Released Tue Nov 19 17:52:44 CET 2019
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency HIGH: many issues fixed, some may have an impact.
|
||||
|
||||
Hi all, Redis 5.0.7 fixes a number of bugs, none is very critical, however
|
||||
there are a few that may have an impact. It's a good idea to upgrade.
|
||||
There are fixes in the area of replication from modules commands and
|
||||
callbacks, AOF fsync (non critical issue), memory leaks (very rare and small),
|
||||
streams beahvior (non critical), and a potential crash in commands
|
||||
processing multiple keys at the same time that is there for years, and happens
|
||||
very rarely, but is not impossible to trigger.
|
||||
|
||||
The following is the list of commits in this release.
|
||||
|
||||
antirez in commit 4d2a31ae:
|
||||
Test: fix implementation-dependent test after code change.
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Oran Agra in commit 9f63fc98:
|
||||
RED-31295 - redis: avoid race between dlopen and thread creation
|
||||
2 files changed, 17 insertions(+)
|
||||
|
||||
antirez in commit 1a9e70c1:
|
||||
Cluster: fix memory leak of cached master.
|
||||
2 files changed, 9 insertions(+), 4 deletions(-)
|
||||
|
||||
Guy Benoish in commit 69b1b5be:
|
||||
Fix usage of server.stream_node_max_*
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
喜欢兰花山丘 in commit 1fd97ee7:
|
||||
Update mkreleasehdr.sh
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 1a9855d7:
|
||||
Remove additional space from comment.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 32a6e3e4:
|
||||
Fix stream test after addition of 0-0 ID test.
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
Yuan Zhou in commit c9e6cda9:
|
||||
aof: fix assignment for aof_fsync_offset
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit d3eeacf9:
|
||||
Merge branch '5.0' of github.com:/antirez/redis into 5.0
|
||||
antirez in commit da5dc458:
|
||||
Rename var to fixed_time_expire now that is more general.
|
||||
4 files changed, 7 insertions(+), 8 deletions(-)
|
||||
|
||||
antirez in commit 0fefed25:
|
||||
Fix patch provided in #6554.
|
||||
1 file changed, 8 insertions(+), 1 deletion(-)
|
||||
|
||||
zhaozhao.zz in commit e9fbc960:
|
||||
expires & blocking: handle ready keys as call()
|
||||
1 file changed, 5 insertions(+)
|
||||
|
||||
Guy Benoish in commit 08ec8f71:
|
||||
XADD with ID 0-0 stores an empty key
|
||||
2 files changed, 14 insertions(+)
|
||||
|
||||
Loris Cro in commit 09e1fe27:
|
||||
fix unreported overflow in autogerenared stream IDs
|
||||
2 files changed, 19 insertions(+), 10 deletions(-)
|
||||
|
||||
Salvatore Sanfilippo in commit 09f9e4b0:
|
||||
Merge pull request #6600 from oranagra/5_module_flags
|
||||
Oran Agra in commit 8d8d68dd:
|
||||
module documentation mismatches: loading and fork child for 5.0 branch
|
||||
3 files changed, 11 insertions(+)
|
||||
|
||||
antirez in commit 7a7fbe70:
|
||||
Modules: RM_GetContextFlags(): remove non Redis 5 features.
|
||||
1 file changed, 3 deletions(-)
|
||||
|
||||
antirez in commit b5830486:
|
||||
Modules: fix moduleCreateArgvFromUserFormat() casting bug.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit b7a2a53a:
|
||||
module: fix propagation API bug.
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 278bd6e3:
|
||||
Modules: add new flags to context, replica state + more.
|
||||
2 files changed, 48 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 61d9a154:
|
||||
Modules: RM_Call(): give pointer to documentation.
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 0e7ea0aa:
|
||||
Modules: RM_Call/Replicate() ability to exclude AOF/replicas.
|
||||
1 file changed, 28 insertions(+), 9 deletions(-)
|
||||
|
||||
antirez in commit 3b38164e:
|
||||
Modules: RM_Replicate() in thread safe contexts.
|
||||
1 file changed, 26 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit ef9fe9b0:
|
||||
Modules: implement RM_Replicate() from async callbacks.
|
||||
2 files changed, 33 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 8066d2a1:
|
||||
Modules: handle propagation when ctx is freed. Flag modules commands ctx.
|
||||
1 file changed, 23 insertions(+), 19 deletions(-)
|
||||
|
||||
antirez in commit d3f4dec4:
|
||||
Update PR #6537: use a fresh time outside call().
|
||||
3 files changed, 27 insertions(+), 7 deletions(-)
|
||||
|
||||
antirez in commit 33f42665:
|
||||
Update PR #6537 patch to for generality.
|
||||
4 files changed, 40 insertions(+), 23 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit 68d71d83:
|
||||
expires: refactoring judgment about whether a key is expired
|
||||
3 files changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 62588dbf:
|
||||
Modules: fix thread safe context creation crash.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.6 Released Wed Sep 25 12:33:56 CEST 2019
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency CRITICAL: Only in case of exposed instances to untrusted users.
|
||||
|
||||
This Redis release, 5.0.6, is a bugfix and enhancement release. The most
|
||||
important bugfix is a corruption related to the HyperLogLog. A malformed
|
||||
HyperLogLog string could cause an invalid access to the memory. At a first
|
||||
glance the vulnerability appears to be not exploitable but just a DoS. The
|
||||
way to trigger the issue is complex, we'll not provide any information about
|
||||
how to do that for the users safety.
|
||||
|
||||
Other significant changes in this release:
|
||||
|
||||
* New modules APIs merged from Redis unstable to Redis 5.
|
||||
* Some memory optimization related to objects creation.
|
||||
* Fixes to flushSlaveOutputBuffer() that make sure that SHUTDOWN will
|
||||
transfer pending buffers to replicas.
|
||||
|
||||
This is the full list of commits:
|
||||
|
||||
antirez in commit 7a41047a:
|
||||
RDB: fix MODULE_AUX loading by continuing to next opcode.
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Oran Agra in commit 4eb3028b:
|
||||
missing per-skiplist overheads in MEMORY USAGE
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
Oran Agra in commit 5d09f9bc:
|
||||
RM_Log - add support for logging without a context or context without module
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit 2810de9f:
|
||||
Cluster: abort loading nodes data if vars arguments are unbalanced.
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit f5c63ce0:
|
||||
More strict checks and better comments in flushSlaveOutputBuffers().
|
||||
1 file changed, 18 insertions(+), 7 deletions(-)
|
||||
|
||||
antirez in commit 7f289c3b:
|
||||
Improve comment in flushSlavesOutputBuffers().
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 7ab62d4b:
|
||||
Replication: clarify why repl_put_online_on_ack exists at all.
|
||||
2 files changed, 34 insertions(+), 10 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit 495dd0da:
|
||||
networking: flushSlavesOutputBuffers bugfix
|
||||
1 file changed, 2 insertions(+), 4 deletions(-)
|
||||
|
||||
Salvatore Sanfilippo in commit c1ccf0f1:
|
||||
Merge pull request #6366 from oranagra/5.0_rm_reply_cstring
|
||||
Salvatore Sanfilippo in commit a50dad73:
|
||||
Merge pull request #6365 from oranagra/5.0_module_aux
|
||||
Oran Agra in commit d6294d05:
|
||||
RM_ReplyWithCString was missing registration
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Oran Agra in commit 8c56fc86:
|
||||
Fix to module aux data rdb format for backwards compatibility with old check-rdb
|
||||
1 file changed, 9 insertions(+), 1 deletion(-)
|
||||
|
||||
Oran Agra in commit 98b1314f:
|
||||
Implement module api for aux data in rdb
|
||||
9 files changed, 431 insertions(+), 18 deletions(-)
|
||||
|
||||
antirez in commit 08b03e23:
|
||||
redis-cli: always report server errors on read errors.
|
||||
1 file changed, 8 insertions(+), 1 deletion(-)
|
||||
|
||||
wubostc in commit 239069de:
|
||||
Reduce the calling stack
|
||||
1 file changed, 2 insertions(+), 3 deletions(-)
|
||||
|
||||
antirez in commit 90bf6313:
|
||||
Make EMBSTR case of #6261 more obvious.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
chendianqiang in commit 2f8a0749:
|
||||
make memory usage consistent of robj with OBJ_ENCODING_INT
|
||||
1 file changed, 9 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit 436ed56d:
|
||||
HyperLogLog: fix the fix of a corruption bug.
|
||||
1 file changed, 1 insertion(+), 2 deletions(-)
|
||||
|
||||
John Sully in commit 680f89fb:
|
||||
Fix HLL corruption bug
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
swilly22 in commit 388efbf8:
|
||||
Extend REDISMODULE_CTX_FLAGS to indicate if redis is currently loading from either RDB or AOF
|
||||
2 files changed, 5 insertions(+)
|
||||
|
||||
Itamar Haber in commit 0ccbdcee:
|
||||
Uses addReplyBulkCString
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Itamar Haber in commit 707a59c6:
|
||||
Adds RedisModule_ReplyWithCString
|
||||
2 files changed, 13 insertions(+)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.5 Released Wed May 15 17:57:41 CEST 2019
|
||||
================================================================================
|
||||
|
||||
Vendored
+1
-1
@@ -274,7 +274,7 @@ int luaD_precall (lua_State *L, StkId func, int nresults) {
|
||||
CallInfo *ci;
|
||||
StkId st, base;
|
||||
Proto *p = cl->p;
|
||||
luaD_checkstack(L, p->maxstacksize);
|
||||
luaD_checkstack(L, p->maxstacksize + p->numparams);
|
||||
func = restorestack(L, funcr);
|
||||
if (!p->is_vararg) { /* no varargs? */
|
||||
base = func + 1;
|
||||
|
||||
Vendored
+6
-4
@@ -89,12 +89,14 @@ typedef struct Header {
|
||||
} Header;
|
||||
|
||||
|
||||
static int getnum (const char **fmt, int df) {
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
if (!isdigit(**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit(**fmt));
|
||||
return a;
|
||||
@@ -115,9 +117,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
||||
case 'f': return sizeof(float);
|
||||
case 'd': return sizeof(double);
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
@@ -150,7 +152,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
||||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@ then
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}"
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
||||
|
||||
@@ -77,6 +77,15 @@ 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
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
|
||||
+17
-3
@@ -327,12 +327,11 @@ listNode *listIndex(list *list, long index) {
|
||||
}
|
||||
|
||||
/* Rotate the list removing the tail node and inserting it to the head. */
|
||||
void listRotate(list *list) {
|
||||
listNode *tail = list->tail;
|
||||
|
||||
void listRotateTailToHead(list *list) {
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
/* Detach current tail */
|
||||
listNode *tail = list->tail;
|
||||
list->tail = tail->prev;
|
||||
list->tail->next = NULL;
|
||||
/* Move it as head */
|
||||
@@ -342,6 +341,21 @@ void listRotate(list *list) {
|
||||
list->head = tail;
|
||||
}
|
||||
|
||||
/* Rotate the list removing the head node and inserting it to the tail. */
|
||||
void listRotateHeadToTail(list *list) {
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
listNode *head = list->head;
|
||||
/* Detach current head */
|
||||
list->head = head->next;
|
||||
list->head->prev = NULL;
|
||||
/* Move it as tail */
|
||||
list->tail->next = head;
|
||||
head->next = NULL;
|
||||
head->prev = list->tail;
|
||||
list->tail = head;
|
||||
}
|
||||
|
||||
/* Add all the elements of the list 'o' at the end of the
|
||||
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
||||
void listJoin(list *l, list *o) {
|
||||
|
||||
+2
-1
@@ -85,7 +85,8 @@ listNode *listSearchKey(list *list, void *key);
|
||||
listNode *listIndex(list *list, long index);
|
||||
void listRewind(list *list, listIter *li);
|
||||
void listRewindTail(list *list, listIter *li);
|
||||
void listRotate(list *list);
|
||||
void listRotateTailToHead(list *list);
|
||||
void listRotateHeadToTail(list *list);
|
||||
void listJoin(list *l, list *o);
|
||||
|
||||
/* Directions for iterators */
|
||||
|
||||
@@ -774,18 +774,26 @@ int loadAppendOnlyFile(char *filename) {
|
||||
argc = atoi(buf+1);
|
||||
if (argc < 1) goto fmterr;
|
||||
|
||||
/* Load the next command in the AOF as our fake client
|
||||
* argv. */
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
/* Parse the argument len. */
|
||||
char *readres = fgets(buf,sizeof(buf),fp);
|
||||
if (readres == NULL || buf[0] != '$') {
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
if (readres == NULL)
|
||||
goto readerr;
|
||||
else
|
||||
goto fmterr;
|
||||
}
|
||||
if (buf[0] != '$') goto fmterr;
|
||||
len = strtol(buf+1,NULL,10);
|
||||
|
||||
/* Read it into a string object. */
|
||||
argsds = sdsnewlen(SDS_NOINIT,len);
|
||||
if (len && fread(argsds,len,1,fp) == 0) {
|
||||
sdsfree(argsds);
|
||||
@@ -794,10 +802,12 @@ int loadAppendOnlyFile(char *filename) {
|
||||
goto readerr;
|
||||
}
|
||||
argv[j] = createObject(OBJ_STRING,argsds);
|
||||
|
||||
/* Discard CRLF. */
|
||||
if (fread(buf,2,1,fp) == 0) {
|
||||
fakeClient->argc = j+1; /* Free up to j. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr; /* discard CRLF */
|
||||
goto readerr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,7 +1137,7 @@ int rioWriteBulkStreamID(rio *r,streamID *id) {
|
||||
int retval;
|
||||
|
||||
sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
|
||||
if ((retval = rioWriteBulkString(r,replyid,sdslen(replyid))) == 0) return 0;
|
||||
retval = rioWriteBulkString(r,replyid,sdslen(replyid));
|
||||
sdsfree(replyid);
|
||||
return retval;
|
||||
}
|
||||
@@ -1761,7 +1771,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_current_size = server.aof_current_size;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
|
||||
/* Clear regular AOF buffer since its contents was just written to
|
||||
* the new AOF from the background rewrite buffer. */
|
||||
@@ -1782,14 +1792,15 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated by signal %d", bysignal);
|
||||
|
||||
+43
-18
@@ -67,6 +67,21 @@
|
||||
|
||||
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
|
||||
|
||||
/* This structure represents the blocked key information that we store
|
||||
* in the client structure. Each client blocked on keys, has a
|
||||
* client->bpop.keys hash table. The keys of the hash table are Redis
|
||||
* keys pointers to 'robj' structures. The value is this structure.
|
||||
* The structure has two goals: firstly we store the list node that this
|
||||
* client uses to be listed in the database "blocked clients for this key"
|
||||
* list, so we can later unblock in O(1) without a list scan.
|
||||
* Secondly for certain blocking types, we have additional info. Right now
|
||||
* the only use for additional info we have is when clients are blocked
|
||||
* on streams, as we have to remember the ID it blocked for. */
|
||||
typedef struct bkinfo {
|
||||
listNode *listnode; /* List node for db->blocking_keys[key] list. */
|
||||
streamID stream_id; /* Stream ID if we blocked in a stream. */
|
||||
} bkinfo;
|
||||
|
||||
/* Get a timeout value from an object and store it into 'timeout'.
|
||||
* The final timeout is always stored as milliseconds as a time where the
|
||||
* timeout will expire, however the parsing is performed according to
|
||||
@@ -262,6 +277,16 @@ void handleClientsBlockedOnKeys(void) {
|
||||
* we can safely call signalKeyAsReady() against this key. */
|
||||
dictDelete(rl->db->ready_keys,rl->key);
|
||||
|
||||
/* Even if we are not inside call(), increment the call depth
|
||||
* in order to make sure that keys are expired against a fixed
|
||||
* reference time, and not against the wallclock time. This
|
||||
* way we can lookup an object multiple times (BRPOPLPUSH does
|
||||
* that) without the risk of it being freed in the second
|
||||
* lookup, invalidating the first one.
|
||||
* See https://github.com/antirez/redis/pull/6554. */
|
||||
server.fixed_time_expire++;
|
||||
updateCachedTime(0);
|
||||
|
||||
/* Serve clients blocked on list key. */
|
||||
robj *o = lookupKeyWrite(rl->db,rl->key);
|
||||
if (o != NULL && o->type == OBJ_LIST) {
|
||||
@@ -281,8 +306,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -343,8 +367,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (receiver->btype != BLOCKED_ZSET) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -388,8 +411,9 @@ void handleClientsBlockedOnKeys(void) {
|
||||
while((ln = listNext(&li))) {
|
||||
client *receiver = listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
bkinfo *bki = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
streamID *gt = &bki->stream_id;
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
@@ -419,7 +443,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
@@ -428,7 +452,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
1);
|
||||
SLC_NONE);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
@@ -457,6 +481,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
server.fixed_time_expire--;
|
||||
|
||||
/* Free this item. */
|
||||
decrRefCount(rl->key);
|
||||
@@ -505,17 +530,15 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
if (target != NULL) incrRefCount(target);
|
||||
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
/* The value associated with the key name in the bpop.keys dictionary
|
||||
* is NULL for lists and sorted sets, or the stream ID for streams. */
|
||||
void *key_data = NULL;
|
||||
if (btype == BLOCKED_STREAM) {
|
||||
key_data = zmalloc(sizeof(streamID));
|
||||
memcpy(key_data,ids+j,sizeof(streamID));
|
||||
}
|
||||
/* Allocate our bkinfo structure, associated to each key the client
|
||||
* is blocked for. */
|
||||
bkinfo *bki = zmalloc(sizeof(*bki));
|
||||
if (btype == BLOCKED_STREAM)
|
||||
bki->stream_id = ids[j];
|
||||
|
||||
/* If the key already exists in the dictionary ignore it. */
|
||||
if (dictAdd(c->bpop.keys,keys[j],key_data) != DICT_OK) {
|
||||
zfree(key_data);
|
||||
if (dictAdd(c->bpop.keys,keys[j],bki) != DICT_OK) {
|
||||
zfree(bki);
|
||||
continue;
|
||||
}
|
||||
incrRefCount(keys[j]);
|
||||
@@ -534,6 +557,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
l = dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(l,c);
|
||||
bki->listnode = listLast(l);
|
||||
}
|
||||
blockClient(c,btype);
|
||||
}
|
||||
@@ -550,11 +574,12 @@ void unblockClientWaitingData(client *c) {
|
||||
/* The client may wait for multiple keys, so unblock it for every key. */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *key = dictGetKey(de);
|
||||
bkinfo *bki = dictGetVal(de);
|
||||
|
||||
/* Remove this client from the list of clients waiting for this key. */
|
||||
l = dictFetchValue(c->db->blocking_keys,key);
|
||||
serverAssertWithInfo(c,key,l != NULL);
|
||||
listDelNode(l,listSearchKey(l,c));
|
||||
listDelNode(l,bki->listnode);
|
||||
/* If the list is empty we need to remove it to avoid wasting memory */
|
||||
if (listLength(l) == 0)
|
||||
dictDelete(c->db->blocking_keys,key);
|
||||
|
||||
+93
-28
@@ -138,6 +138,7 @@ int clusterLoadConfig(char *filename) {
|
||||
/* Handle the special "vars" line. Don't pretend it is the last
|
||||
* line even if it actually is when generated by Redis. */
|
||||
if (strcasecmp(argv[0],"vars") == 0) {
|
||||
if (!(argc % 2)) goto fmterr;
|
||||
for (j = 1; j < argc; j += 2) {
|
||||
if (strcasecmp(argv[j],"currentEpoch") == 0) {
|
||||
server.cluster->currentEpoch =
|
||||
@@ -156,7 +157,10 @@ int clusterLoadConfig(char *filename) {
|
||||
}
|
||||
|
||||
/* Regular config lines have at least eight fields */
|
||||
if (argc < 8) goto fmterr;
|
||||
if (argc < 8) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
|
||||
/* Create this node if it does not exist */
|
||||
n = clusterLookupNode(argv[0]);
|
||||
@@ -165,7 +169,10 @@ int clusterLoadConfig(char *filename) {
|
||||
clusterAddNode(n);
|
||||
}
|
||||
/* Address and port */
|
||||
if ((p = strrchr(argv[1],':')) == NULL) goto fmterr;
|
||||
if ((p = strrchr(argv[1],':')) == NULL) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
*p = '\0';
|
||||
memcpy(n->ip,argv[1],strlen(argv[1])+1);
|
||||
char *port = p+1;
|
||||
@@ -246,7 +253,10 @@ int clusterLoadConfig(char *filename) {
|
||||
*p = '\0';
|
||||
direction = p[1]; /* Either '>' or '<' */
|
||||
slot = atoi(argv[j]+1);
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
p += 3;
|
||||
cn = clusterLookupNode(p);
|
||||
if (!cn) {
|
||||
@@ -266,8 +276,12 @@ int clusterLoadConfig(char *filename) {
|
||||
} else {
|
||||
start = stop = atoi(argv[j]);
|
||||
}
|
||||
if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (start < 0 || start >= CLUSTER_SLOTS ||
|
||||
stop < 0 || stop >= CLUSTER_SLOTS)
|
||||
{
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
while(start <= stop) clusterAddSlot(n, start++);
|
||||
}
|
||||
|
||||
@@ -707,6 +721,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
||||
node->slaves = NULL;
|
||||
node->slaveof = NULL;
|
||||
node->ping_sent = node->pong_received = 0;
|
||||
node->data_received = 0;
|
||||
node->fail_time = 0;
|
||||
node->link = NULL;
|
||||
memset(node->ip,0,sizeof(node->ip));
|
||||
@@ -1420,7 +1435,10 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
}
|
||||
} else {
|
||||
/* If it's not in NOADDR state and we don't have it, we
|
||||
* start a handshake process against this IP/PORT pairs.
|
||||
* add it to our trusted dict with exact nodeid and flag.
|
||||
* Note that we cannot simply start a handshake against
|
||||
* this IP/PORT pairs, since IP/PORT can be reused already,
|
||||
* otherwise we risk joining another cluster.
|
||||
*
|
||||
* Note that we require that the sender of this gossip message
|
||||
* is a well known node in our cluster, otherwise we risk
|
||||
@@ -1429,7 +1447,12 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
!(flags & CLUSTER_NODE_NOADDR) &&
|
||||
!clusterBlacklistExists(g->nodename))
|
||||
{
|
||||
clusterStartHandshake(g->ip,ntohs(g->port),ntohs(g->cport));
|
||||
clusterNode *node;
|
||||
node = createClusterNode(g->nodename, flags);
|
||||
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
|
||||
node->port = ntohs(g->port);
|
||||
node->cport = ntohs(g->cport);
|
||||
clusterAddNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1636,6 +1659,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
|
||||
uint32_t totlen = ntohl(hdr->totlen);
|
||||
uint16_t type = ntohs(hdr->type);
|
||||
mstime_t now = mstime();
|
||||
|
||||
if (type < CLUSTERMSG_TYPE_COUNT)
|
||||
server.cluster->stats_bus_messages_received[type]++;
|
||||
@@ -1699,6 +1723,13 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
|
||||
/* Check if the sender is a known node. */
|
||||
sender = clusterLookupNode(hdr->sender);
|
||||
|
||||
/* Update the last time we saw any data from this node. We
|
||||
* use this in order to avoid detecting a timeout from a node that
|
||||
* is just sending a lot of data in the cluster bus, for instance
|
||||
* because of Pub/Sub. */
|
||||
if (sender) sender->data_received = now;
|
||||
|
||||
if (sender && !nodeInHandshake(sender)) {
|
||||
/* Update our curretEpoch if we see a newer epoch in the cluster. */
|
||||
senderCurrentEpoch = ntohu64(hdr->currentEpoch);
|
||||
@@ -1713,7 +1744,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
}
|
||||
/* Update the replication offset info for this node. */
|
||||
sender->repl_offset = ntohu64(hdr->offset);
|
||||
sender->repl_offset_time = mstime();
|
||||
sender->repl_offset_time = now;
|
||||
/* If we are a slave performing a manual failover and our master
|
||||
* sent its offset while already paused, populate the MF state. */
|
||||
if (server.cluster->mf_end &&
|
||||
@@ -1827,7 +1858,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
* address. */
|
||||
serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
|
||||
link->node->name,
|
||||
(int)(mstime()-(link->node->ctime)),
|
||||
(int)(now-(link->node->ctime)),
|
||||
link->node->flags);
|
||||
link->node->flags |= CLUSTER_NODE_NOADDR;
|
||||
link->node->ip[0] = '\0';
|
||||
@@ -1862,7 +1893,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
|
||||
/* Update our info about the node */
|
||||
if (link->node && type == CLUSTERMSG_TYPE_PONG) {
|
||||
link->node->pong_received = mstime();
|
||||
link->node->pong_received = now;
|
||||
link->node->ping_sent = 0;
|
||||
|
||||
/* The PFAIL condition can be reversed without external
|
||||
@@ -2009,7 +2040,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
"FAIL message received from %.40s about %.40s",
|
||||
hdr->sender, hdr->data.fail.about.nodename);
|
||||
failing->flags |= CLUSTER_NODE_FAIL;
|
||||
failing->fail_time = mstime();
|
||||
failing->fail_time = now;
|
||||
failing->flags &= ~CLUSTER_NODE_PFAIL;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
CLUSTER_TODO_UPDATE_STATE);
|
||||
@@ -2062,9 +2093,9 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* Manual failover requested from slaves. Initialize the state
|
||||
* accordingly. */
|
||||
resetManualFailover();
|
||||
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_slave = sender;
|
||||
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
|
||||
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT));
|
||||
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
||||
sender->name);
|
||||
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
|
||||
@@ -3472,7 +3503,6 @@ void clusterCron(void) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
now = mstime(); /* Use an updated time at every iteration. */
|
||||
mstime_t delay;
|
||||
|
||||
if (node->flags &
|
||||
(CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
|
||||
@@ -3496,7 +3526,7 @@ void clusterCron(void) {
|
||||
this_slaves = okslaves;
|
||||
}
|
||||
|
||||
/* If we are waiting for the PONG more than half the cluster
|
||||
/* If we are not receiving any data for more than half the cluster
|
||||
* timeout, reconnect the link: maybe there is a connection
|
||||
* issue even if the node is alive. */
|
||||
if (node->link && /* is connected */
|
||||
@@ -3505,7 +3535,9 @@ void clusterCron(void) {
|
||||
node->ping_sent && /* we already sent a ping */
|
||||
node->pong_received < node->ping_sent && /* still waiting pong */
|
||||
/* and we are waiting for the pong more than timeout/2 */
|
||||
now - node->ping_sent > server.cluster_node_timeout/2)
|
||||
now - node->ping_sent > server.cluster_node_timeout/2 &&
|
||||
/* and in such interval we are not seeing any traffic at all. */
|
||||
now - node->data_received > server.cluster_node_timeout/2)
|
||||
{
|
||||
/* Disconnect the link, it will be reconnected automatically. */
|
||||
freeClusterLink(node->link);
|
||||
@@ -3540,7 +3572,13 @@ void clusterCron(void) {
|
||||
/* Compute the delay of the PONG. Note that if we already received
|
||||
* the PONG, then node->ping_sent is zero, so can't reach this
|
||||
* code at all. */
|
||||
delay = now - node->ping_sent;
|
||||
mstime_t delay = now - node->ping_sent;
|
||||
|
||||
/* We consider every incoming data as proof of liveness, since
|
||||
* our cluster bus link is also used for data: under heavy data
|
||||
* load pong delays are possible. */
|
||||
mstime_t data_delay = now - node->data_received;
|
||||
if (data_delay < delay) delay = data_delay;
|
||||
|
||||
if (delay > server.cluster_node_timeout) {
|
||||
/* Timeout reached. Set the node as possibly failing if it is
|
||||
@@ -4134,11 +4172,17 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
int j = 0, start = -1;
|
||||
int i, nested_elements = 0;
|
||||
|
||||
/* Skip slaves (that are iterated when producing the output of their
|
||||
* master) and masters not serving any slot. */
|
||||
if (!nodeIsMaster(node) || node->numslots == 0) continue;
|
||||
|
||||
for(i = 0; i < node->numslaves; i++) {
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
nested_elements++;
|
||||
}
|
||||
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
int bit, i;
|
||||
|
||||
@@ -4146,8 +4190,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addDeferredMultiBulkLength(c);
|
||||
addReplyMultiBulkLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
@@ -4177,9 +4220,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
}
|
||||
}
|
||||
@@ -4893,7 +4934,8 @@ void restoreCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Make sure this key does not already exist here... */
|
||||
if (!replace && lookupKeyWrite(c->db,c->argv[1]) != NULL) {
|
||||
robj *key = c->argv[1];
|
||||
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
|
||||
addReply(c,shared.busykeyerr);
|
||||
return;
|
||||
}
|
||||
@@ -4915,23 +4957,37 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,key)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove the old key if needed. */
|
||||
if (replace) dbDelete(c->db,c->argv[1]);
|
||||
int deleted = 0;
|
||||
if (replace)
|
||||
deleted = dbDelete(c->db,key);
|
||||
|
||||
if (ttl && !absttl) ttl+=mstime();
|
||||
if (ttl && checkAlreadyExpired(ttl)) {
|
||||
if (deleted) {
|
||||
rewriteClientCommandVector(c,2,shared.del,key);
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
decrRefCount(obj);
|
||||
addReply(c, shared.ok);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the key and set the TTL if any */
|
||||
dbAdd(c->db,c->argv[1],obj);
|
||||
dbAdd(c->db,key,obj);
|
||||
if (ttl) {
|
||||
if (!absttl) ttl+=mstime();
|
||||
setExpire(c,c->db,c->argv[1],ttl);
|
||||
setExpire(c,c->db,key,ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,key);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -5685,6 +5741,15 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
int slot = keyHashSlot((char*)key->ptr, sdslen(key->ptr));
|
||||
clusterNode *node = server.cluster->slots[slot];
|
||||
|
||||
/* if the client is read-only and attempting to access key that our
|
||||
* replica can handle, allow it. */
|
||||
if ((c->flags & CLIENT_READONLY) &&
|
||||
(c->lastcmd->flags & CMD_READONLY) &&
|
||||
nodeIsSlave(myself) && myself->slaveof == node)
|
||||
{
|
||||
node = myself;
|
||||
}
|
||||
|
||||
/* We send an error and unblock the client if:
|
||||
* 1) The slot is unassigned, emitting a cluster down error.
|
||||
* 2) The slot is not handled by this node, nor being imported. */
|
||||
|
||||
@@ -128,6 +128,7 @@ typedef struct clusterNode {
|
||||
tables. */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t data_received; /* Unix time we received any data */
|
||||
mstime_t fail_time; /* Unix time when FAIL flag was set */
|
||||
mstime_t voted_time; /* Last time we voted for a slave of this master */
|
||||
mstime_t repl_offset_time; /* Unix time we received offset for this node */
|
||||
|
||||
@@ -542,7 +542,7 @@ void keysCommand(client *c) {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
allkeys = (pattern[0] == '*' && plen == 1);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj;
|
||||
@@ -1129,6 +1129,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
/* Check if the key is expired. */
|
||||
int keyIsExpired(redisDb *db, robj *key) {
|
||||
mstime_t when = getExpire(db,key);
|
||||
mstime_t now;
|
||||
|
||||
if (when < 0) return 0; /* No expire for this key */
|
||||
|
||||
@@ -1140,8 +1141,26 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
|
||||
if (server.lua_caller) {
|
||||
now = server.lua_time_start;
|
||||
}
|
||||
/* 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
|
||||
* cached time, that we update before each call in the call() function.
|
||||
* This way we avoid that commands such as RPOPLPUSH or similar, that
|
||||
* may re-open the same key multiple times, can invalidate an already
|
||||
* open object in a next call, if the next call will see the key expired,
|
||||
* while the first did not. */
|
||||
else if (server.fixed_time_expire > 0) {
|
||||
now = server.mstime;
|
||||
}
|
||||
/* For the other cases, we want to use the most fresh time we have. */
|
||||
else {
|
||||
now = mstime();
|
||||
}
|
||||
|
||||
/* The key expired if the current (virtual or real) time is greater
|
||||
* than the expire time of the key. */
|
||||
return now > when;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -355,7 +355,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
sdsele = ln->value;
|
||||
if ((newsds = activeDefragSds(sdsele))) {
|
||||
/* When defragging an sds value, we need to update the dict key */
|
||||
uint64_t hash = dictGetHash(d, sdsele);
|
||||
uint64_t hash = dictGetHash(d, newsds);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
ln->value = newsds;
|
||||
defragged++;
|
||||
|
||||
+11
-7
@@ -391,6 +391,16 @@ void flushSlaveKeysWithExpireList(void) {
|
||||
}
|
||||
}
|
||||
|
||||
int checkAlreadyExpired(long long when) {
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
*
|
||||
* Instead we add the already expired key to the database with expire time
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
return (when <= mstime() && !server.loading && !server.masterhost);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Expires Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -418,13 +428,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
*
|
||||
* Instead we take the other branch of the IF statement setting an expire
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
if (when <= mstime() && !server.loading && !server.masterhost) {
|
||||
if (checkAlreadyExpired(when)) {
|
||||
robj *aux;
|
||||
|
||||
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
|
||||
|
||||
+2
-1
@@ -700,7 +700,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
p += oplen;
|
||||
first += span;
|
||||
}
|
||||
if (span == 0) return -1; /* Invalid format. */
|
||||
if (span == 0 || p >= end) return -1; /* Invalid format. */
|
||||
|
||||
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
|
||||
if (next >= end) next = NULL;
|
||||
@@ -1535,6 +1535,7 @@ void pfdebugCommand(client *c) {
|
||||
sds decoded = sdsempty();
|
||||
|
||||
if (hdr->encoding != HLL_SPARSE) {
|
||||
sdsfree(decoded);
|
||||
addReplyError(c,"HLL encoding is not sparse");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,30 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
} else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
|
||||
dict *ht = obj->ptr;
|
||||
return dictSize(ht);
|
||||
} else if (obj->type == OBJ_STREAM) {
|
||||
size_t effort = 0;
|
||||
stream *s = obj->ptr;
|
||||
|
||||
/* Make a best effort estimate to maintain constant runtime. Every macro
|
||||
* node in the Stream is one allocation. */
|
||||
effort += s->rax->numnodes;
|
||||
|
||||
/* 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) {
|
||||
raxIterator ri;
|
||||
streamCG *cg;
|
||||
raxStart(&ri,s->cgroups);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
/* There must be at least one group so the following should always
|
||||
* work. */
|
||||
serverAssert(raxNext(&ri));
|
||||
cg = ri.data;
|
||||
effort += raxSize(s->cgroups)*(1+raxSize(cg->pel));
|
||||
raxStop(&ri);
|
||||
}
|
||||
return effort;
|
||||
} else {
|
||||
return 1; /* Everything else is a single allocation. */
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n
|
||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||
BUILD_ID=`uname -n`"-"`date +%s`
|
||||
if [ -n "$SOURCE_DATE_EPOCH" ]; then
|
||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
|
||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u +%s)
|
||||
fi
|
||||
test -f release.h || touch release.h
|
||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||
|
||||
+222
-46
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "rdb.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define REDISMODULE_CORE 1
|
||||
@@ -132,10 +133,14 @@ struct RedisModuleCtx {
|
||||
int keys_count;
|
||||
|
||||
struct RedisModulePoolAllocBlock *pa_head;
|
||||
redisOpArray saved_oparray; /* When propagating commands in a callback
|
||||
we reallocate the "also propagate" op
|
||||
array. Here we save the old one to
|
||||
restore it later. */
|
||||
};
|
||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL, {0}}
|
||||
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
|
||||
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
|
||||
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
|
||||
@@ -143,6 +148,7 @@ typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
|
||||
#define REDISMODULE_CTX_THREAD_SAFE (1<<5)
|
||||
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
|
||||
#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<7)
|
||||
|
||||
/* This represents a Redis key opened with RM_OpenKey(). */
|
||||
struct RedisModuleKey {
|
||||
@@ -291,6 +297,11 @@ typedef struct RedisModuleCommandFilter {
|
||||
/* Registered filters */
|
||||
static list *moduleCommandFilters;
|
||||
|
||||
/* Flags for moduleCreateArgvFromUserFormat(). */
|
||||
#define REDISMODULE_ARGV_REPLICATE (1<<0)
|
||||
#define REDISMODULE_ARGV_NO_AOF (1<<1)
|
||||
#define REDISMODULE_ARGV_NO_REPLICAS (1<<2)
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -460,7 +471,8 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) {
|
||||
case OBJ_LIST: isempty = listTypeLength(o) == 0; break;
|
||||
case OBJ_SET: isempty = setTypeSize(o) == 0; break;
|
||||
case OBJ_ZSET: isempty = zsetLength(o) == 0; break;
|
||||
case OBJ_HASH : isempty = hashTypeLength(o) == 0; break;
|
||||
case OBJ_HASH: isempty = hashTypeLength(o) == 0; break;
|
||||
case OBJ_STREAM: isempty = streamLength(o) == 0; break;
|
||||
default: isempty = 0;
|
||||
}
|
||||
|
||||
@@ -496,8 +508,49 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Helper function for when a command callback is called, in order to handle
|
||||
* details needed to correctly replicate commands. */
|
||||
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
client *c = ctx->client;
|
||||
|
||||
/* We don't need to do anything here if the context was never used
|
||||
* in order to propagate commands. */
|
||||
if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
|
||||
|
||||
if (c->flags & CLIENT_LUA) return;
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrapped around MULTI/EXEC. */
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
|
||||
/* If this is not a module command context (but is instead a simple
|
||||
* callback context), we have to handle directly the "also propagate"
|
||||
* array and emit it. In a module command call this will be handled
|
||||
* directly by call(). */
|
||||
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL) &&
|
||||
server.also_propagate.numops)
|
||||
{
|
||||
for (int j = 0; j < server.also_propagate.numops; j++) {
|
||||
redisOp *rop = &server.also_propagate.ops[j];
|
||||
int target = rop->target;
|
||||
if (target)
|
||||
propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
|
||||
}
|
||||
redisOpArrayFree(&server.also_propagate);
|
||||
/* Restore the previous oparray in case of nexted use of the API. */
|
||||
server.also_propagate = ctx->saved_oparray;
|
||||
/* We're done with saved_oparray, let's invalidate it. */
|
||||
redisOpArrayInit(&ctx->saved_oparray);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the context after the user function was called. */
|
||||
void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
moduleHandlePropagationAfterCommandCallback(ctx);
|
||||
autoMemoryCollect(ctx);
|
||||
poolAllocRelease(ctx);
|
||||
if (ctx->postponed_arrays) {
|
||||
@@ -513,34 +566,16 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
|
||||
}
|
||||
|
||||
/* Helper function for when a command callback is called, in order to handle
|
||||
* details needed to correctly replicate commands. */
|
||||
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
client *c = ctx->client;
|
||||
|
||||
if (c->flags & CLIENT_LUA) return;
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrapped around MULTI/EXEC. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/* This Redis command binds the normal Redis command invocation with commands
|
||||
* exported by modules. */
|
||||
void RedisModuleCommandDispatcher(client *c) {
|
||||
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
|
||||
ctx.flags |= REDISMODULE_CTX_MODULE_COMMAND_CALL;
|
||||
ctx.module = cp->module;
|
||||
ctx.client = c;
|
||||
cp->func(&ctx,(void**)c->argv,c->argc);
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
|
||||
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
|
||||
@@ -615,9 +650,9 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
|
||||
* flags into the command flags used by the Redis core.
|
||||
*
|
||||
* It returns the set of flags, or -1 if unknown flags are found. */
|
||||
int commandFlagsFromString(char *s) {
|
||||
int64_t commandFlagsFromString(char *s) {
|
||||
int count, j;
|
||||
int flags = 0;
|
||||
int64_t flags = 0;
|
||||
sds *tokens = sdssplitlen(s,strlen(s)," ",1,&count);
|
||||
for (j = 0; j < count; j++) {
|
||||
char *t = tokens[j];
|
||||
@@ -695,7 +730,7 @@ int commandFlagsFromString(char *s) {
|
||||
* other reason.
|
||||
*/
|
||||
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
|
||||
int flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
||||
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
||||
if (flags == -1) return REDISMODULE_ERR;
|
||||
if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled)
|
||||
return REDISMODULE_ERR;
|
||||
@@ -1241,6 +1276,17 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Reply with a bulk string, taking in input a C buffer pointer that is
|
||||
* assumed to be null-terminated.
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReplyBulkCString(c,(char*)buf);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Reply with a bulk string, taking in input a RedisModuleString object.
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
@@ -1303,9 +1349,16 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
/* If we already emitted MULTI return ASAP. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||
/* If this is a thread safe context, we do not want to wrap commands
|
||||
* executed into MUTLI/EXEC, they are executed as single commands
|
||||
* executed into MULTI/EXEC, they are executed as single commands
|
||||
* from an external client in essence. */
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
|
||||
/* If this is a callback context, and not a module command execution
|
||||
* context, we have to setup the op array for the "also propagate" API
|
||||
* so that RM_Replicate() will work. */
|
||||
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) {
|
||||
ctx->saved_oparray = server.also_propagate;
|
||||
redisOpArrayInit(&server.also_propagate);
|
||||
}
|
||||
execCommandPropagateMulti(ctx->client);
|
||||
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
|
||||
}
|
||||
@@ -1327,6 +1380,24 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* Please refer to RedisModule_Call() for more information.
|
||||
*
|
||||
* Using the special "A" and "R" modifiers, the caller can exclude either
|
||||
* the AOF or the replicas from the propagation of the specified command.
|
||||
* Otherwise, by default, the command will be propagated in both channels.
|
||||
*
|
||||
* ## Note about calling this function from a thread safe context:
|
||||
*
|
||||
* Normally when you call this function from the callback implementing a
|
||||
* module command, or any other callback provided by the Redis Module API,
|
||||
* Redis will accumulate all the calls to this function in the context of
|
||||
* the callback, and will propagate all the commands wrapped in a MULTI/EXEC
|
||||
* transaction. However when calling this function from a threaded safe context
|
||||
* that can live an undefined amount of time, and can be locked/unlocked in
|
||||
* at will, the behavior is different: MULTI/EXEC wrapper is not emitted
|
||||
* and the command specified is inserted in the AOF and replication stream
|
||||
* immediately.
|
||||
*
|
||||
* ## Return value
|
||||
*
|
||||
* The command returns REDISMODULE_ERR if the format specifiers are invalid
|
||||
* or the command name does not belong to a known command. */
|
||||
int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
||||
@@ -1344,10 +1415,23 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
|
||||
va_end(ap);
|
||||
if (argv == NULL) return REDISMODULE_ERR;
|
||||
|
||||
/* Replicate! */
|
||||
moduleReplicateMultiIfNeeded(ctx);
|
||||
alsoPropagate(cmd,ctx->client->db->id,argv,argc,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
/* Select the propagation target. Usually is AOF + replicas, however
|
||||
* the caller can exclude one or the other using the "A" or "R"
|
||||
* modifiers. */
|
||||
int target = 0;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL;
|
||||
|
||||
/* Replicate! When we are in a threaded context, we want to just insert
|
||||
* the replicated command ASAP, since it is not clear when the context
|
||||
* will stop being used, so accumulating stuff does not make much sense,
|
||||
* nor we could easily use the alsoPropagate() API from threads. */
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
|
||||
propagate(cmd,ctx->client->db->id,argv,argc,target);
|
||||
} else {
|
||||
moduleReplicateMultiIfNeeded(ctx);
|
||||
alsoPropagate(cmd,ctx->client->db->id,argv,argc,target);
|
||||
}
|
||||
|
||||
/* Release the argv. */
|
||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||
@@ -1436,6 +1520,23 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_OOM_WARNING: Less than 25% of memory remains before
|
||||
* reaching the maxmemory level.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_LOADING: Server is loading RDB/AOF
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE: No active link with the master.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING: The replica is trying to
|
||||
* connect with the master.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING: Master -> Replica RDB
|
||||
* transfer is in progress.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE: The replica has an active link
|
||||
* with its master. This is the
|
||||
* contrary of STALE state.
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_ACTIVE_CHILD: There is currently some background
|
||||
* process active (RDB, AUX or module).
|
||||
*/
|
||||
int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
|
||||
@@ -1454,6 +1555,9 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
if (server.cluster_enabled)
|
||||
flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
|
||||
|
||||
if (server.loading)
|
||||
flags |= REDISMODULE_CTX_FLAGS_LOADING;
|
||||
|
||||
/* Maxmemory and eviction policy */
|
||||
if (server.maxmemory > 0) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
|
||||
@@ -1475,6 +1579,20 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_SLAVE;
|
||||
if (server.repl_slave_ro)
|
||||
flags |= REDISMODULE_CTX_FLAGS_READONLY;
|
||||
|
||||
/* Replica state flags. */
|
||||
if (server.repl_state == REPL_STATE_CONNECT ||
|
||||
server.repl_state == REPL_STATE_CONNECTING)
|
||||
{
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING;
|
||||
} else if (server.repl_state == REPL_STATE_TRANSFER) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING;
|
||||
} else if (server.repl_state == REPL_STATE_CONNECTED) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE;
|
||||
}
|
||||
|
||||
if (server.repl_state != REPL_STATE_CONNECTED)
|
||||
flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE;
|
||||
}
|
||||
|
||||
/* OOM flag. */
|
||||
@@ -1483,6 +1601,9 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
if (retval == C_ERR) flags |= REDISMODULE_CTX_FLAGS_OOM;
|
||||
if (level > 0.75) flags |= REDISMODULE_CTX_FLAGS_OOM_WARNING;
|
||||
|
||||
/* Presence of children processes. */
|
||||
if (hasActiveChildProcess()) flags |= REDISMODULE_CTX_FLAGS_ACTIVE_CHILD;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -1566,6 +1687,7 @@ int RM_KeyType(RedisModuleKey *key) {
|
||||
case OBJ_ZSET: return REDISMODULE_KEYTYPE_ZSET;
|
||||
case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH;
|
||||
case OBJ_MODULE: return REDISMODULE_KEYTYPE_MODULE;
|
||||
/* case OBJ_STREAM: return REDISMODULE_KEYTYPE_STREAM; - don't wanna add new API to 5.0 */
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -1583,6 +1705,7 @@ size_t RM_ValueLength(RedisModuleKey *key) {
|
||||
case OBJ_SET: return setTypeSize(key->value);
|
||||
case OBJ_ZSET: return zsetLength(key->value);
|
||||
case OBJ_HASH: return hashTypeLength(key->value);
|
||||
case OBJ_STREAM: return streamLength(key->value);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -2662,12 +2785,11 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
|
||||
* to special modifiers in "fmt". For now only one exists:
|
||||
*
|
||||
* "!" -> REDISMODULE_ARGV_REPLICATE
|
||||
* "A" -> REDISMODULE_ARGV_NO_AOF
|
||||
* "R" -> REDISMODULE_ARGV_NO_REPLICAS
|
||||
*
|
||||
* On error (format specifier error) NULL is returned and nothing is
|
||||
* allocated. On success the argument vector is returned. */
|
||||
|
||||
#define REDISMODULE_ARGV_REPLICATE (1<<0)
|
||||
|
||||
robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
|
||||
int argc = 0, argv_size, j;
|
||||
robj **argv = NULL;
|
||||
@@ -2696,7 +2818,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
size_t len = va_arg(ap,size_t);
|
||||
argv[argc++] = createStringObject(buf,len);
|
||||
} else if (*p == 'l') {
|
||||
long ll = va_arg(ap,long long);
|
||||
long long ll = va_arg(ap,long long);
|
||||
argv[argc++] = createObject(OBJ_STRING,sdsfromlonglong(ll));
|
||||
} else if (*p == 'v') {
|
||||
/* A vector of strings */
|
||||
@@ -2716,6 +2838,10 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
}
|
||||
} else if (*p == '!') {
|
||||
if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
|
||||
} else if (*p == 'A') {
|
||||
if (flags) (*flags) |= REDISMODULE_ARGV_NO_AOF;
|
||||
} else if (*p == 'R') {
|
||||
if (flags) (*flags) |= REDISMODULE_ARGV_NO_REPLICAS;
|
||||
} else {
|
||||
goto fmterr;
|
||||
}
|
||||
@@ -2736,7 +2862,10 @@ fmterr:
|
||||
* NULL is returned and errno is set to the following values:
|
||||
*
|
||||
* EINVAL: command non existing, wrong arity, wrong format specifier.
|
||||
* EPERM: operation in Cluster instance with key in non local slot. */
|
||||
* EPERM: operation in Cluster instance with key in non local slot.
|
||||
*
|
||||
* This API is documented here: https://redis.io/topics/modules-intro
|
||||
*/
|
||||
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
||||
struct redisCommand *cmd;
|
||||
client *c = NULL;
|
||||
@@ -2806,8 +2935,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
/* Run the command */
|
||||
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
|
||||
if (replicate) {
|
||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_AOF))
|
||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
|
||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||
}
|
||||
call(c,call_flags);
|
||||
|
||||
@@ -3062,6 +3193,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
|
||||
moduleTypeMemUsageFunc mem_usage;
|
||||
moduleTypeDigestFunc digest;
|
||||
moduleTypeFreeFunc free;
|
||||
struct {
|
||||
moduleTypeAuxLoadFunc aux_load;
|
||||
moduleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
} v2;
|
||||
} *tms = (struct typemethods*) typemethods_ptr;
|
||||
|
||||
moduleType *mt = zcalloc(sizeof(*mt));
|
||||
@@ -3073,6 +3209,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
|
||||
mt->mem_usage = tms->mem_usage;
|
||||
mt->digest = tms->digest;
|
||||
mt->free = tms->free;
|
||||
if (tms->version >= 2) {
|
||||
mt->aux_load = tms->v2.aux_load;
|
||||
mt->aux_save = tms->v2.aux_save;
|
||||
mt->aux_save_triggers = tms->v2.aux_save_triggers;
|
||||
}
|
||||
memcpy(mt->name,name,sizeof(mt->name));
|
||||
listAddNodeTail(ctx->module->types,mt);
|
||||
return mt;
|
||||
@@ -3339,6 +3480,36 @@ loaderr:
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
|
||||
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
|
||||
* who asked for it. */
|
||||
ssize_t rdbSaveModulesAux(rio *rdb, int when) {
|
||||
size_t total_written = 0;
|
||||
dictIterator *di = dictGetIterator(modules);
|
||||
dictEntry *de;
|
||||
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct RedisModule *module = dictGetVal(de);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(module->types,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
moduleType *mt = ln->value;
|
||||
if (!mt->aux_save || !(mt->aux_save_triggers & when))
|
||||
continue;
|
||||
ssize_t ret = rdbSaveSingleModuleAux(rdb, when, mt);
|
||||
if (ret==-1) {
|
||||
dictReleaseIterator(di);
|
||||
return -1;
|
||||
}
|
||||
total_written += ret;
|
||||
}
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
return total_written;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Key digest API (DEBUG DIGEST interface for modules types)
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3499,7 +3670,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
|
||||
|
||||
if (level < server.verbosity) return;
|
||||
|
||||
name_len = snprintf(msg, sizeof(msg),"<%s> ", module->name);
|
||||
name_len = snprintf(msg, sizeof(msg),"<%s> ", module? module->name: "module");
|
||||
vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap);
|
||||
serverLogRaw(level,msg);
|
||||
}
|
||||
@@ -3517,13 +3688,15 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
|
||||
* There is a fixed limit to the length of the log line this function is able
|
||||
* to emit, this limit is not specified but is guaranteed to be more than
|
||||
* a few lines of text.
|
||||
*
|
||||
* The ctx argument may be NULL if cannot be provided in the context of the
|
||||
* caller for instance threads or callbacks, in which case a generic "module"
|
||||
* will be used instead of the module name.
|
||||
*/
|
||||
void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) {
|
||||
if (!ctx->module) return; /* Can only log if module is initialized */
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
RM_LogRaw(ctx->module,levelstr,fmt,ap);
|
||||
RM_LogRaw(ctx? ctx->module: NULL,levelstr,fmt,ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@@ -3725,7 +3898,6 @@ void moduleHandleBlockedClients(void) {
|
||||
ctx.client = bc->client;
|
||||
ctx.blocked_client = bc;
|
||||
bc->reply_callback(&ctx,(void**)c->argv,c->argc);
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
|
||||
@@ -3840,9 +4012,9 @@ int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* To call non-reply APIs, the thread safe context must be prepared with:
|
||||
*
|
||||
* RedisModule_ThreadSafeCallStart(ctx);
|
||||
* RedisModule_ThreadSafeContextLock(ctx);
|
||||
* ... make your call here ...
|
||||
* RedisModule_ThreadSafeCallStop(ctx);
|
||||
* RedisModule_ThreadSafeContextUnlock(ctx);
|
||||
*
|
||||
* This is not needed when using `RedisModule_Reply*` functions, assuming
|
||||
* that a blocked client was used when the context was created, otherwise
|
||||
@@ -3866,7 +4038,7 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
|
||||
ctx->client = createClient(-1);
|
||||
if (bc) {
|
||||
selectDb(ctx->client,bc->dbid);
|
||||
ctx->client->id = bc->client->id;
|
||||
if (bc->client) ctx->client->id = bc->client->id;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -4210,10 +4382,13 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
||||
UNUSED(ctx);
|
||||
|
||||
clusterNode *node = clusterLookupNode(id);
|
||||
if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
|
||||
if (node == NULL ||
|
||||
node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
|
||||
{
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
if (ip) memcpy(ip,node->name,REDISMODULE_NODE_ID_LEN);
|
||||
if (ip) strncpy(ip,node->ip,NET_IP_STR_LEN);
|
||||
|
||||
if (master_id) {
|
||||
/* If the information is not available, the function will set the
|
||||
@@ -5323,6 +5498,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(ReplySetArrayLength);
|
||||
REGISTER_API(ReplyWithString);
|
||||
REGISTER_API(ReplyWithStringBuffer);
|
||||
REGISTER_API(ReplyWithCString);
|
||||
REGISTER_API(ReplyWithNull);
|
||||
REGISTER_API(ReplyWithCallReply);
|
||||
REGISTER_API(ReplyWithDouble);
|
||||
|
||||
+29
-21
@@ -918,7 +918,7 @@ void freeClient(client *c) {
|
||||
/* We need to remember the time when we started to have zero
|
||||
* attached slaves, as after some time we'll free the replication
|
||||
* backlog. */
|
||||
if (c->flags & CLIENT_SLAVE && listLength(server.slaves) == 0)
|
||||
if (getClientType(c) == CLIENT_TYPE_SLAVE && listLength(server.slaves) == 0)
|
||||
server.repl_no_slaves_since = server.unixtime;
|
||||
refreshGoodSlavesCount();
|
||||
}
|
||||
@@ -1030,8 +1030,8 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
* just deliver as much data as it is possible to deliver.
|
||||
*
|
||||
* Moreover, we also send as much as possible if the client is
|
||||
* a slave (otherwise, on high-speed traffic, the replication
|
||||
* buffer will grow indefinitely) */
|
||||
* a slave or a monitor (otherwise, on high-speed traffic, the
|
||||
* replication/output buffer will grow indefinitely) */
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(server.maxmemory == 0 ||
|
||||
zmalloc_used_memory() < server.maxmemory) &&
|
||||
@@ -1216,7 +1216,7 @@ int processInlineBuffer(client *c) {
|
||||
/* Newline from slaves can be used to refresh the last ACK time.
|
||||
* This is useful for a slave to ping back while loading a big
|
||||
* RDB file. */
|
||||
if (querylen == 0 && c->flags & CLIENT_SLAVE)
|
||||
if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
|
||||
c->repl_ack_time = server.unixtime;
|
||||
|
||||
/* Move querybuffer position to the next query in the buffer. */
|
||||
@@ -1230,12 +1230,8 @@ int processInlineBuffer(client *c) {
|
||||
|
||||
/* Create redis objects for all arguments. */
|
||||
for (c->argc = 0, j = 0; j < argc; j++) {
|
||||
if (sdslen(argv[j])) {
|
||||
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
|
||||
c->argc++;
|
||||
} else {
|
||||
sdsfree(argv[j]);
|
||||
}
|
||||
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
|
||||
c->argc++;
|
||||
}
|
||||
zfree(argv);
|
||||
return C_OK;
|
||||
@@ -2037,12 +2033,14 @@ unsigned long getClientOutputBufferMemoryUsage(client *c) {
|
||||
*
|
||||
* The function will return one of the following:
|
||||
* CLIENT_TYPE_NORMAL -> Normal client
|
||||
* CLIENT_TYPE_SLAVE -> Slave or client executing MONITOR command
|
||||
* CLIENT_TYPE_SLAVE -> Slave
|
||||
* CLIENT_TYPE_PUBSUB -> Client subscribed to Pub/Sub channels
|
||||
* CLIENT_TYPE_MASTER -> The client representing our replication master.
|
||||
*/
|
||||
int getClientType(client *c) {
|
||||
if (c->flags & CLIENT_MASTER) return CLIENT_TYPE_MASTER;
|
||||
/* Even though MONITOR clients are marked as replicas, we
|
||||
* want the expose them as normal clients. */
|
||||
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR))
|
||||
return CLIENT_TYPE_SLAVE;
|
||||
if (c->flags & CLIENT_PUBSUB) return CLIENT_TYPE_PUBSUB;
|
||||
@@ -2143,17 +2141,27 @@ void flushSlavesOutputBuffers(void) {
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = listNodeValue(ln);
|
||||
int events;
|
||||
int events = aeGetFileEvents(server.el,slave->fd);
|
||||
int can_receive_writes = (events & AE_WRITABLE) ||
|
||||
(slave->flags & CLIENT_PENDING_WRITE);
|
||||
|
||||
/* Note that the following will not flush output buffers of slaves
|
||||
* in STATE_ONLINE but having put_online_on_ack set to true: in this
|
||||
* case the writable event is never installed, since the purpose
|
||||
* of put_online_on_ack is to postpone the moment it is installed.
|
||||
* This is what we want since slaves in this state should not receive
|
||||
* writes before the first ACK. */
|
||||
events = aeGetFileEvents(server.el,slave->fd);
|
||||
if (events & AE_WRITABLE &&
|
||||
slave->replstate == SLAVE_STATE_ONLINE &&
|
||||
/* We don't want to send the pending data to the replica in a few
|
||||
* cases:
|
||||
*
|
||||
* 1. For some reason there is neither the write handler installed
|
||||
* nor the client is flagged as to have pending writes: for some
|
||||
* reason this replica may not be set to receive data. This is
|
||||
* just for the sake of defensive programming.
|
||||
*
|
||||
* 2. The put_online_on_ack flag is true. To know why we don't want
|
||||
* to send data to the replica in this case, please grep for the
|
||||
* flag for this flag.
|
||||
*
|
||||
* 3. Obviously if the slave is not ONLINE.
|
||||
*/
|
||||
if (slave->replstate == SLAVE_STATE_ONLINE &&
|
||||
can_receive_writes &&
|
||||
!slave->repl_put_online_on_ack &&
|
||||
clientHasPendingReplies(slave))
|
||||
{
|
||||
writeToClient(slave->fd,slave,0);
|
||||
|
||||
+12
-5
@@ -467,10 +467,15 @@ robj *tryObjectEncoding(robj *o) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = OBJ_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
if (o->encoding == OBJ_ENCODING_RAW) {
|
||||
sdsfree(o->ptr);
|
||||
o->encoding = OBJ_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
} else if (o->encoding == OBJ_ENCODING_EMBSTR) {
|
||||
decrRefCount(o);
|
||||
return createStringObjectFromLongLongForValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,7 +839,9 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
d = ((zset*)o->ptr)->dict;
|
||||
zskiplist *zsl = ((zset*)o->ptr)->zsl;
|
||||
zskiplistNode *znode = zsl->header->level[0].forward;
|
||||
asize = sizeof(*o)+sizeof(zset)+(sizeof(struct dictEntry*)*dictSlots(d));
|
||||
asize = sizeof(*o)+sizeof(zset)+sizeof(zskiplist)+sizeof(dict)+
|
||||
(sizeof(struct dictEntry*)*dictSlots(d))+
|
||||
zmalloc_size(zsl->header);
|
||||
while(znode != NULL && samples < sample_size) {
|
||||
elesize += sdsAllocSize(znode->ele);
|
||||
elesize += sizeof(struct dictEntry) + zmalloc_size(znode);
|
||||
|
||||
@@ -966,7 +966,6 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
@@ -975,10 +974,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
io.bytes += retval;
|
||||
|
||||
/* Then write the module-specific representation + EOF marker. */
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
mt->rdb_save(&io,mv->value);
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
if (retval == -1)
|
||||
io.error = 1;
|
||||
else
|
||||
io.bytes += retval;
|
||||
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@@ -1091,6 +1093,47 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
|
||||
/* Save a module-specific aux value. */
|
||||
RedisModuleIO io;
|
||||
int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
retval = rdbSaveLen(rdb,mt->id);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* write the 'when' so that we can provide it on loading. add a UINT opcode
|
||||
* for backwards compatibility, everything after the MT needs to be prefixed
|
||||
* by an opcode. */
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_UINT);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
retval = rdbSaveLen(rdb,when);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* Then write the module-specific representation + EOF marker. */
|
||||
moduleInitIOContext(io,mt,rdb,NULL);
|
||||
mt->aux_save(&io,when);
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||
if (retval == -1)
|
||||
io.error = 1;
|
||||
else
|
||||
io.bytes += retval;
|
||||
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
if (io.error)
|
||||
return -1;
|
||||
return io.bytes;
|
||||
}
|
||||
|
||||
/* Produces a dump of the database in RDB format sending it to the specified
|
||||
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
|
||||
* is returned and part of the output, or all the output, can be
|
||||
@@ -1112,6 +1155,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
||||
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
||||
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
@@ -1173,6 +1217,8 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
}
|
||||
|
||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
|
||||
|
||||
/* EOF opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||
|
||||
@@ -1724,8 +1770,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Error reading the consumer name from Stream group");
|
||||
}
|
||||
streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
|
||||
1);
|
||||
streamConsumer *consumer =
|
||||
streamLookupConsumer(cgroup,cname,SLC_NONE);
|
||||
sdsfree(cname);
|
||||
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
|
||||
@@ -1837,7 +1883,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
/* The DB can take some non trivial amount of time to load. Update
|
||||
* our cached time since it is used to create and update the last
|
||||
* interaction time with clients and for other important things. */
|
||||
updateCachedTime();
|
||||
updateCachedTime(0);
|
||||
if (server.masterhost && server.repl_state == REPL_STATE_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
@@ -1974,15 +2020,14 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
decrRefCount(auxval);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_MODULE_AUX) {
|
||||
/* This is just for compatibility with the future: we have plans
|
||||
* to add the ability for modules to store anything in the RDB
|
||||
* file, like data that is not related to the Redis key space.
|
||||
* Such data will potentially be stored both before and after the
|
||||
* RDB keys-values section. For this reason since RDB version 9,
|
||||
* we have the ability to read a MODULE_AUX opcode followed by an
|
||||
* identifier of the module, and a serialized value in "MODULE V2"
|
||||
* format. */
|
||||
/* Load module data that is not related to the Redis key space.
|
||||
* Such data can be potentially be stored both before and after the
|
||||
* RDB keys-values section. */
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
int when_opcode = rdbLoadLen(rdb,NULL);
|
||||
int when = rdbLoadLen(rdb,NULL);
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT)
|
||||
rdbExitReportCorruptRDB("bad when_opcode");
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
@@ -1992,14 +2037,37 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
|
||||
exit(1);
|
||||
} else if (!rdbCheckMode && mt != NULL) {
|
||||
/* This version of Redis actually does not know what to do
|
||||
* with modules AUX data... */
|
||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load for the module '%s'. Probably you want to use a newer version of Redis which implements aux data callbacks", name);
|
||||
exit(1);
|
||||
if (!mt->aux_load) {
|
||||
/* Module doesn't support AUX. */
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data, but the module '%s' doesn't seem to support it.", name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,NULL);
|
||||
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) || 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);
|
||||
}
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
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);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
/* RDB check mode. */
|
||||
robj *aux = rdbLoadCheckModuleValue(rdb,name);
|
||||
decrRefCount(aux);
|
||||
continue; /* Read next opcode. */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,8 @@ size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
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);
|
||||
robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||
|
||||
+18
-1
@@ -58,6 +58,7 @@ struct {
|
||||
#define RDB_CHECK_DOING_CHECK_SUM 5
|
||||
#define RDB_CHECK_DOING_READ_LEN 6
|
||||
#define RDB_CHECK_DOING_READ_AUX 7
|
||||
#define RDB_CHECK_DOING_READ_MODULE_AUX 8
|
||||
|
||||
char *rdb_check_doing_string[] = {
|
||||
"start",
|
||||
@@ -67,7 +68,8 @@ char *rdb_check_doing_string[] = {
|
||||
"read-object-value",
|
||||
"check-sum",
|
||||
"read-len",
|
||||
"read-aux"
|
||||
"read-aux",
|
||||
"read-module-aux"
|
||||
};
|
||||
|
||||
char *rdb_type_string[] = {
|
||||
@@ -270,6 +272,21 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
decrRefCount(auxkey);
|
||||
decrRefCount(auxval);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_MODULE_AUX) {
|
||||
/* AUX: Auxiliary data for modules. */
|
||||
uint64_t moduleid, when_opcode, when;
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_MODULE_AUX;
|
||||
if ((moduleid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
if ((when_opcode = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
if ((when = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
rdbCheckInfo("MODULE AUX for: %s", name);
|
||||
|
||||
robj *o = rdbLoadCheckModuleValue(&rdb,name);
|
||||
decrRefCount(o);
|
||||
continue; /* Read type again. */
|
||||
} else {
|
||||
if (!rdbIsObjectType(type)) {
|
||||
rdbCheckError("Invalid object type: %d", type);
|
||||
|
||||
+19
-7
@@ -1144,7 +1144,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
for (j = 0; j < argc; j++)
|
||||
argvlen[j] = sdslen(argv[j]);
|
||||
|
||||
while(repeat-- > 0) {
|
||||
while(repeat < 0 || repeat-- > 0) {
|
||||
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
|
||||
while (config.monitor_mode) {
|
||||
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
|
||||
@@ -1179,6 +1179,11 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
cliSelect();
|
||||
}
|
||||
}
|
||||
if (config.cluster_reissue_command){
|
||||
/* If we need to reissue the command, break to prevent a
|
||||
further 'repeat' number of dud interations */
|
||||
break;
|
||||
}
|
||||
if (config.interval) usleep(config.interval);
|
||||
fflush(stdout); /* Make it grep friendly */
|
||||
}
|
||||
@@ -1589,12 +1594,12 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
|
||||
cliPrintContextError();
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
/* Issue the command again if we got redirected in cluster mode */
|
||||
if (config.cluster_mode && config.cluster_reissue_command) {
|
||||
}
|
||||
/* Issue the command again if we got redirected in cluster mode */
|
||||
if (config.cluster_mode && config.cluster_reissue_command) {
|
||||
cliConnect(CC_FORCE);
|
||||
} else {
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
@@ -6073,6 +6078,7 @@ static void pipeMode(void) {
|
||||
/* Handle the readable state: we can read replies from the server. */
|
||||
if (mask & AE_READABLE) {
|
||||
ssize_t nread;
|
||||
int read_error = 0;
|
||||
|
||||
/* Read from socket and feed the hiredis reader. */
|
||||
do {
|
||||
@@ -6080,7 +6086,8 @@ static void pipeMode(void) {
|
||||
if (nread == -1 && errno != EAGAIN && errno != EINTR) {
|
||||
fprintf(stderr, "Error reading from the server: %s\n",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
read_error = 1;
|
||||
break;
|
||||
}
|
||||
if (nread > 0) {
|
||||
redisReaderFeed(reader,ibuf,nread);
|
||||
@@ -6113,6 +6120,11 @@ static void pipeMode(void) {
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
} while(reply);
|
||||
|
||||
/* Abort on read errors. We abort here because it is important
|
||||
* to consume replies even after a read error: this way we can
|
||||
* show a potential problem to the user. */
|
||||
if (read_error) exit(1);
|
||||
}
|
||||
|
||||
/* Handle the writable state: we can send protocol to the server. */
|
||||
|
||||
+30
-2
@@ -87,7 +87,24 @@
|
||||
#define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11)
|
||||
/* The command was sent over the replication link. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
|
||||
|
||||
/* Redis is currently loading either from AOF or RDB. */
|
||||
#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
|
||||
/* The replica has no link with its master, note that
|
||||
* there is the inverse flag as well:
|
||||
*
|
||||
* REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE
|
||||
*
|
||||
* The two flags are exclusive, one or the other can be set. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE (1<<14)
|
||||
/* The replica is trying to connect with the master.
|
||||
* (REPL_STATE_CONNECT and REPL_STATE_CONNECTING states) */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING (1<<15)
|
||||
/* THe replica is receiving an RDB file from its master. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING (1<<16)
|
||||
/* The replica is online, receiving updates from its master. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17)
|
||||
/* There is currently some background process active. */
|
||||
#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18)
|
||||
|
||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
||||
@@ -126,6 +143,10 @@
|
||||
|
||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||
|
||||
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
|
||||
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||
|
||||
/* This type represents a timer handle, and is returned when a timer is
|
||||
* registered and used in order to invalidate a timer. It's just a 64 bit
|
||||
* number, because this is how each timer is represented inside the radix tree
|
||||
@@ -163,6 +184,8 @@ typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlocke
|
||||
typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when);
|
||||
typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
||||
@@ -171,7 +194,7 @@ typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const cha
|
||||
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 1
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||
typedef struct RedisModuleTypeMethods {
|
||||
uint64_t version;
|
||||
RedisModuleTypeLoadFunc rdb_load;
|
||||
@@ -180,6 +203,9 @@ typedef struct RedisModuleTypeMethods {
|
||||
RedisModuleTypeMemUsageFunc mem_usage;
|
||||
RedisModuleTypeDigestFunc digest;
|
||||
RedisModuleTypeFreeFunc free;
|
||||
RedisModuleTypeAuxLoadFunc aux_load;
|
||||
RedisModuleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
} RedisModuleTypeMethods;
|
||||
|
||||
#define REDISMODULE_GET_API(name) \
|
||||
@@ -225,6 +251,7 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx,
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||
@@ -375,6 +402,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ReplyWithArray);
|
||||
REDISMODULE_GET_API(ReplySetArrayLength);
|
||||
REDISMODULE_GET_API(ReplyWithStringBuffer);
|
||||
REDISMODULE_GET_API(ReplyWithCString);
|
||||
REDISMODULE_GET_API(ReplyWithString);
|
||||
REDISMODULE_GET_API(ReplyWithNull);
|
||||
REDISMODULE_GET_API(ReplyWithCallReply);
|
||||
|
||||
+48
-10
@@ -823,7 +823,9 @@ void replconfCommand(client *c) {
|
||||
c->repl_ack_time = server.unixtime;
|
||||
/* If this was a diskless replication, we need to really put
|
||||
* the slave online when the first ACK is received (which
|
||||
* confirms slave is online and ready to get more data). */
|
||||
* confirms slave is online and ready to get more data). This
|
||||
* allows for simpler and less CPU intensive EOF detection
|
||||
* when streaming RDB files. */
|
||||
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
|
||||
putSlaveOnline(c);
|
||||
/* Note: this command does not reply anything! */
|
||||
@@ -842,18 +844,20 @@ void replconfCommand(client *c) {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* This function puts a slave in the online state, and should be called just
|
||||
* after a slave received the RDB file for the initial synchronization, and
|
||||
/* This function puts a replica in the online state, and should be called just
|
||||
* after a replica received the RDB file for the initial synchronization, and
|
||||
* we are finally ready to send the incremental stream of commands.
|
||||
*
|
||||
* It does a few things:
|
||||
*
|
||||
* 1) Put the slave in ONLINE state (useless when the function is called
|
||||
* because state is already ONLINE but repl_put_online_on_ack is true).
|
||||
* 1) Put the slave in ONLINE state. Note that the function may also be called
|
||||
* for a replicas that are already in ONLINE state, but having the flag
|
||||
* repl_put_online_on_ack set to true: we still have to install the write
|
||||
* handler in that case. This function will take care of that.
|
||||
* 2) Make sure the writable event is re-installed, since calling the SYNC
|
||||
* command disables it, so that we can accumulate output buffer without
|
||||
* sending it to the slave.
|
||||
* 3) Update the count of good slaves. */
|
||||
* sending it to the replica.
|
||||
* 3) Update the count of "good replicas". */
|
||||
void putSlaveOnline(client *slave) {
|
||||
slave->replstate = SLAVE_STATE_ONLINE;
|
||||
slave->repl_put_online_on_ack = 0;
|
||||
@@ -965,11 +969,31 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Streamed RDB transfer with replica %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
|
||||
replicationGetSlaveName(slave));
|
||||
/* Note: we wait for a REPLCONF ACK message from slave in
|
||||
/* Note: we wait for a REPLCONF ACK message from the replica in
|
||||
* order to really put it online (install the write handler
|
||||
* so that the accumulated data can be transferred). However
|
||||
* we change the replication state ASAP, since our slave
|
||||
* is technically online now. */
|
||||
* is technically online now.
|
||||
*
|
||||
* So things work like that:
|
||||
*
|
||||
* 1. We end trasnferring the RDB file via socket.
|
||||
* 2. The replica is put ONLINE but the write handler
|
||||
* is not installed.
|
||||
* 3. The replica however goes really online, and pings us
|
||||
* back via REPLCONF ACK commands.
|
||||
* 4. Now we finally install the write handler, and send
|
||||
* the buffers accumulated so far to the replica.
|
||||
*
|
||||
* But why we do that? Because the replica, when we stream
|
||||
* the RDB directly via the socket, must detect the RDB
|
||||
* EOF (end of file), that is a special random string at the
|
||||
* end of the RDB (for streamed RDBs we don't know the length
|
||||
* in advance). Detecting such final EOF string is much
|
||||
* simpler and less CPU intensive if no more data is sent
|
||||
* after such final EOF. So we don't want to glue the end of
|
||||
* the RDB trasfer with the start of the other replication
|
||||
* data. */
|
||||
slave->replstate = SLAVE_STATE_ONLINE;
|
||||
slave->repl_put_online_on_ack = 1;
|
||||
slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */
|
||||
@@ -1268,6 +1292,17 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
}
|
||||
|
||||
/* Make sure the new file (also used for persistence) is fully synced
|
||||
* (not covered by earlier calls to rdb_fsync_range). */
|
||||
if (fsync(server.repl_transfer_fd) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed trying to sync the temp DB to disk in "
|
||||
"MASTER <-> REPLICA synchronization: %s",
|
||||
strerror(errno));
|
||||
cancelReplicationHandshake();
|
||||
return;
|
||||
}
|
||||
|
||||
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
|
||||
serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> REPLICA synchronization: %s", strerror(errno));
|
||||
cancelReplicationHandshake();
|
||||
@@ -1972,7 +2007,10 @@ void replicationSetMaster(char *ip, int port) {
|
||||
cancelReplicationHandshake();
|
||||
/* Before destroying our master state, create a cached master using
|
||||
* our own parameters, to later PSYNC with the new master. */
|
||||
if (was_master) replicationCacheMasterUsingMyself();
|
||||
if (was_master) {
|
||||
replicationDiscardCachedMaster();
|
||||
replicationCacheMasterUsingMyself();
|
||||
}
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -521,12 +521,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
!server.loading && /* Don't care about mem if loading. */
|
||||
!server.masterhost && /* Slave must execute the script. */
|
||||
server.lua_write_dirty == 0 && /* Script had no side effects so far. */
|
||||
server.lua_oom && /* Detected OOM when script start. */
|
||||
(cmd->flags & CMD_DENYOOM))
|
||||
{
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (cmd->flags & CMD_RANDOM) server.lua_random_dirty = 1;
|
||||
@@ -2213,6 +2212,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||||
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
|
||||
lua_pop(lua,1);
|
||||
sdsfree(code);
|
||||
sdsfree(expr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
const char *SDS_NOINIT;
|
||||
extern const char *SDS_NOINIT;
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
+2
-1
@@ -1061,6 +1061,7 @@ int sentinelTryConnectionSharing(sentinelRedisInstance *ri) {
|
||||
releaseInstanceLink(ri->link,NULL);
|
||||
ri->link = match->link;
|
||||
match->link->refcount++;
|
||||
dictReleaseIterator(di);
|
||||
return C_OK;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
@@ -4270,7 +4271,7 @@ void sentinelFailoverDetectEnd(sentinelRedisInstance *master) {
|
||||
sentinelRedisInstance *slave = dictGetVal(de);
|
||||
int retval;
|
||||
|
||||
if (slave->flags & (SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
|
||||
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
|
||||
if (slave->link->disconnected) continue;
|
||||
|
||||
retval = sentinelSendSlaveOf(slave,
|
||||
|
||||
+69
-23
@@ -236,7 +236,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
|
||||
{"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0},
|
||||
{"dbsize",dbsizeCommand,1,"rF",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"sltMF",0,NULL,0,0,0,0,0},
|
||||
{"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0},
|
||||
{"save",saveCommand,1,"as",0,NULL,0,0,0,0,0},
|
||||
@@ -779,6 +779,11 @@ void updateDictResizePolicy(void) {
|
||||
dictDisableResize();
|
||||
}
|
||||
|
||||
int hasActiveChildProcess() {
|
||||
return server.rdb_child_pid != -1 ||
|
||||
server.aof_child_pid != -1;
|
||||
}
|
||||
|
||||
/* ======================= Cron: called every 100 ms ======================== */
|
||||
|
||||
/* Add a sample to the operations per second array of samples. */
|
||||
@@ -816,7 +821,7 @@ int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
|
||||
if (server.maxidletime &&
|
||||
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves */
|
||||
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves and monitors */
|
||||
!(c->flags & CLIENT_MASTER) && /* no timeout for masters */
|
||||
!(c->flags & CLIENT_BLOCKED) && /* no timeout for BLPOP */
|
||||
!(c->flags & CLIENT_PUBSUB) && /* no timeout for Pub/Sub clients */
|
||||
@@ -982,7 +987,7 @@ void clientsCron(void) {
|
||||
/* Rotate the list, take the current head, process.
|
||||
* This way if the client must be removed from the list it's the
|
||||
* first element and we don't incur into O(N) computation. */
|
||||
listRotate(server.clients);
|
||||
listRotateTailToHead(server.clients);
|
||||
head = listFirst(server.clients);
|
||||
c = listNodeValue(head);
|
||||
/* The following functions do different service checks on the client.
|
||||
@@ -1054,19 +1059,30 @@ 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) */
|
||||
void updateCachedTime(void) {
|
||||
time_t unixtime = time(NULL);
|
||||
* 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();
|
||||
server.mstime = server.ustime / 1000;
|
||||
time_t unixtime = server.mstime / 1000;
|
||||
atomicSet(server.unixtime,unixtime);
|
||||
server.mstime = mstime();
|
||||
|
||||
/* To get information about daylight saving time, we need to call localtime_r
|
||||
* and cache the result. However calling localtime_r in this context is safe
|
||||
* since we will never fork() while here, in the main thread. The logging
|
||||
* function will call a thread safe version of localtime that has no locks. */
|
||||
struct tm tm;
|
||||
localtime_r(&server.unixtime,&tm);
|
||||
server.daylight_active = tm.tm_isdst;
|
||||
/* To get information about daylight saving time, we need to call
|
||||
* localtime_r and cache the result. However calling localtime_r in this
|
||||
* context is safe since we will never fork() while here, in the main
|
||||
* thread. The logging function will call a thread safe version of
|
||||
* localtime that has no locks. */
|
||||
if (update_daylight_info) {
|
||||
struct tm tm;
|
||||
time_t ut = server.unixtime;
|
||||
localtime_r(&ut,&tm);
|
||||
server.daylight_active = tm.tm_isdst;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is our timer interrupt, called server.hz times per second.
|
||||
@@ -1099,7 +1115,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
|
||||
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
updateCachedTime(1);
|
||||
|
||||
server.hz = server.config_hz;
|
||||
/* Adapt the server.hz value to the number of configured clients. If we have
|
||||
@@ -1523,7 +1539,7 @@ void initServerConfig(void) {
|
||||
pthread_mutex_init(&server.lruclock_mutex,NULL);
|
||||
pthread_mutex_init(&server.unixtime_mutex,NULL);
|
||||
|
||||
updateCachedTime();
|
||||
updateCachedTime(1);
|
||||
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
|
||||
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
|
||||
changeReplicationId();
|
||||
@@ -2027,6 +2043,7 @@ void initServer(void) {
|
||||
server.hz = server.config_hz;
|
||||
server.pid = getpid();
|
||||
server.current_client = NULL;
|
||||
server.fixed_time_expire = 0;
|
||||
server.clients = listCreate();
|
||||
server.clients_index = raxNew();
|
||||
server.clients_to_close = listCreate();
|
||||
@@ -2179,6 +2196,14 @@ void initServer(void) {
|
||||
scriptingInit(1);
|
||||
slowlogInit();
|
||||
latencyMonitorInit();
|
||||
}
|
||||
|
||||
/* Some steps in server initialization need to be done last (after modules
|
||||
* are loaded).
|
||||
* Specifically, creation of threads due to a race bug in ld.so, in which
|
||||
* Thread Local Storage initialization collides with dlopen call.
|
||||
* see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
|
||||
void InitServerLast() {
|
||||
bioInit();
|
||||
server.initial_memory_usage = zmalloc_used_memory();
|
||||
}
|
||||
@@ -2311,8 +2336,13 @@ struct redisCommand *lookupCommandOrOriginal(sds name) {
|
||||
* + PROPAGATE_AOF (propagate into the AOF file if is enabled)
|
||||
* + PROPAGATE_REPL (propagate into the replication link)
|
||||
*
|
||||
* This should not be used inside commands implementation. Use instead
|
||||
* alsoPropagate(), preventCommandPropagation(), forceCommandPropagation().
|
||||
* This should not be used inside commands implementation since it will not
|
||||
* wrap the resulting commands in MULTI/EXEC. Use instead alsoPropagate(),
|
||||
* preventCommandPropagation(), forceCommandPropagation().
|
||||
*
|
||||
* However for functions that need to (also) propagate out of the context of a
|
||||
* command execution, for example when serving a blocked client, you
|
||||
* want to use propagate().
|
||||
*/
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
|
||||
int flags)
|
||||
@@ -2414,10 +2444,13 @@ void preventCommandReplication(client *c) {
|
||||
*
|
||||
*/
|
||||
void call(client *c, int flags) {
|
||||
long long dirty, start, duration;
|
||||
long long dirty;
|
||||
ustime_t start, duration;
|
||||
int client_old_flags = c->flags;
|
||||
struct redisCommand *real_cmd = c->cmd;
|
||||
|
||||
server.fixed_time_expire++;
|
||||
|
||||
/* Sent the command to clients in MONITOR mode, only if the commands are
|
||||
* not generated from reading an AOF. */
|
||||
if (listLength(server.monitors) &&
|
||||
@@ -2435,7 +2468,8 @@ void call(client *c, int flags) {
|
||||
|
||||
/* Call the command. */
|
||||
dirty = server.dirty;
|
||||
start = ustime();
|
||||
updateCachedTime(0);
|
||||
start = server.ustime;
|
||||
c->cmd->proc(c);
|
||||
duration = ustime()-start;
|
||||
dirty = server.dirty-dirty;
|
||||
@@ -2531,6 +2565,7 @@ void call(client *c, int flags) {
|
||||
redisOpArrayFree(&server.also_propagate);
|
||||
}
|
||||
server.also_propagate = prev_also_propagate;
|
||||
server.fixed_time_expire--;
|
||||
server.stat_numcommands++;
|
||||
}
|
||||
|
||||
@@ -2632,6 +2667,13 @@ int processCommand(client *c) {
|
||||
addReply(c, shared.oomerr);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Save out_of_memory result at script start, otherwise if we check OOM
|
||||
* untill first write within script, memory used by lua stack and
|
||||
* arguments might interfere. */
|
||||
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) {
|
||||
server.lua_oom = out_of_memory;
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't accept write commands if there are problems persisting on disk
|
||||
@@ -3864,12 +3906,14 @@ void loadDataFromDisk(void) {
|
||||
(float)(ustime()-start)/1000000);
|
||||
|
||||
/* Restore the replication ID / offset from the RDB file. */
|
||||
if ((server.masterhost || (server.cluster_enabled && nodeIsSlave(server.cluster->myself)))&&
|
||||
if ((server.masterhost ||
|
||||
(server.cluster_enabled &&
|
||||
nodeIsSlave(server.cluster->myself))) &&
|
||||
rsi.repl_id_is_set &&
|
||||
rsi.repl_offset != -1 &&
|
||||
/* Note that older implementations may save a repl_stream_db
|
||||
* of -1 inside the RDB file in a wrong way, see more information
|
||||
* in function rdbPopulateSaveInfo. */
|
||||
* of -1 inside the RDB file in a wrong way, see more
|
||||
* information in function rdbPopulateSaveInfo. */
|
||||
rsi.repl_stream_db != -1)
|
||||
{
|
||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||
@@ -4173,6 +4217,7 @@ int main(int argc, char **argv) {
|
||||
linuxMemoryWarnings();
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
InitServerLast();
|
||||
loadDataFromDisk();
|
||||
if (server.cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
@@ -4187,6 +4232,7 @@ int main(int argc, char **argv) {
|
||||
if (server.sofd > 0)
|
||||
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
||||
} else {
|
||||
InitServerLast();
|
||||
sentinelIsRunning();
|
||||
}
|
||||
|
||||
|
||||
+22
-4
@@ -50,6 +50,7 @@
|
||||
#include <signal.h>
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
typedef long long ustime_t; /* microsecond time type. */
|
||||
|
||||
#include "ae.h" /* Event driven programming library */
|
||||
#include "sds.h" /* Dynamic safe strings */
|
||||
@@ -489,6 +490,10 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
|
||||
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
|
||||
|
||||
/* Bit flags for moduleTypeAuxSaveFunc */
|
||||
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||
|
||||
struct RedisModule;
|
||||
struct RedisModuleIO;
|
||||
struct RedisModuleDigest;
|
||||
@@ -501,6 +506,8 @@ struct redisObject;
|
||||
* is deleted. */
|
||||
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
|
||||
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
|
||||
typedef int (*moduleTypeAuxLoadFunc)(struct RedisModuleIO *rdb, int encver, int when);
|
||||
typedef void (*moduleTypeAuxSaveFunc)(struct RedisModuleIO *rdb, int when);
|
||||
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
|
||||
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
|
||||
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
||||
@@ -517,6 +524,9 @@ typedef struct RedisModuleType {
|
||||
moduleTypeMemUsageFunc mem_usage;
|
||||
moduleTypeDigestFunc digest;
|
||||
moduleTypeFreeFunc free;
|
||||
moduleTypeAuxLoadFunc aux_load;
|
||||
moduleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
|
||||
} moduleType;
|
||||
|
||||
@@ -739,7 +749,7 @@ typedef struct client {
|
||||
int flags; /* Client flags: CLIENT_* macros. */
|
||||
int authenticated; /* When requirepass is non-NULL. */
|
||||
int replstate; /* Replication state if this is a slave. */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on first ACK. */
|
||||
int repldbfd; /* Replication DB file descriptor. */
|
||||
off_t repldboff; /* Replication DB file offset. */
|
||||
off_t repldbsize; /* Replication DB file size. */
|
||||
@@ -979,7 +989,8 @@ struct redisServer {
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
list *clients_pending_write; /* There is to write or install handler. */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
client *current_client; /* Current client, only used on crash report */
|
||||
client *current_client; /* Current client executing the command. */
|
||||
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
||||
@@ -1222,7 +1233,8 @@ struct redisServer {
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
time_t timezone; /* Cached timezone. As set by tzset(). */
|
||||
int daylight_active; /* Currently in daylight saving time. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
mstime_t mstime; /* 'unixtime' in milliseconds. */
|
||||
ustime_t ustime; /* 'unixtime' in microseconds. */
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
@@ -1265,6 +1277,7 @@ struct redisServer {
|
||||
execution. */
|
||||
int lua_kill; /* Kill the script if true. */
|
||||
int lua_always_replicate_commands; /* Default replication type. */
|
||||
int lua_oom; /* OOM detected when script start? */
|
||||
/* Lazy free */
|
||||
int lazyfree_lazy_eviction;
|
||||
int lazyfree_lazy_expire;
|
||||
@@ -1411,6 +1424,7 @@ void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
void moduleCallCommandFilters(client *c);
|
||||
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@@ -1640,6 +1654,7 @@ void openChildInfoPipe(void);
|
||||
void closeChildInfoPipe(void);
|
||||
void sendChildInfo(int process_type);
|
||||
void receiveChildInfo(void);
|
||||
int hasActiveChildProcess();
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
@@ -1718,6 +1733,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
|
||||
void call(client *c, int flags);
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
||||
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
||||
void redisOpArrayInit(redisOpArray *oa);
|
||||
void redisOpArrayFree(redisOpArray *oa);
|
||||
void forceCommandPropagation(client *c, int flags);
|
||||
void preventCommandPropagation(client *c);
|
||||
void preventCommandAOF(client *c);
|
||||
@@ -1738,7 +1755,7 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(void);
|
||||
void updateCachedTime(int update_daylight_info);
|
||||
void resetServerStats(void);
|
||||
void activeDefragCycle(void);
|
||||
unsigned int getLRUClock(void);
|
||||
@@ -1817,6 +1834,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy);
|
||||
int expireIfNeeded(redisDb *db, robj *key);
|
||||
long long getExpire(redisDb *db, robj *key);
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when);
|
||||
int checkAlreadyExpired(long long when);
|
||||
robj *lookupKey(redisDb *db, robj *key, int flags);
|
||||
robj *lookupKeyRead(redisDb *db, robj *key);
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key);
|
||||
|
||||
+8
-1
@@ -96,18 +96,25 @@ typedef struct sreamPropInfo {
|
||||
/* Prototypes of exported APIs. */
|
||||
struct client;
|
||||
|
||||
/* Flags for streamLookupConsumer */
|
||||
#define SLC_NONE 0
|
||||
#define SLC_NOCREAT (1<<0) /* Do not create the consumer if it doesn't exist */
|
||||
#define SLC_NOREFRESH (1<<1) /* Do not update consumer's seen-time */
|
||||
|
||||
stream *streamNew(void);
|
||||
void freeStream(stream *s);
|
||||
unsigned long streamLength(const robj *subject);
|
||||
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
|
||||
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
|
||||
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
|
||||
void streamIteratorGetField(streamIterator *si, unsigned char **fieldptr, unsigned char **valueptr, int64_t *fieldlen, int64_t *valuelen);
|
||||
void streamIteratorStop(streamIterator *si);
|
||||
streamCG *streamLookupCG(stream *s, sds groupname);
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create);
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags);
|
||||
streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
|
||||
streamNACK *streamCreateNACK(streamConsumer *consumer);
|
||||
void streamDecodeID(void *buf, streamID *id);
|
||||
int streamCompareID(streamID *a, streamID *b);
|
||||
void streamIncrID(streamID *id);
|
||||
|
||||
#endif
|
||||
|
||||
+107
-40
@@ -67,6 +67,27 @@ void freeStream(stream *s) {
|
||||
zfree(s);
|
||||
}
|
||||
|
||||
/* Set 'id' to be its successor streamID */
|
||||
void streamIncrID(streamID *id) {
|
||||
if (id->seq == UINT64_MAX) {
|
||||
if (id->ms == UINT64_MAX) {
|
||||
/* Special case where 'id' is the last possible streamID... */
|
||||
id->ms = id->seq = 0;
|
||||
} else {
|
||||
id->ms++;
|
||||
id->seq = 0;
|
||||
}
|
||||
} else {
|
||||
id->seq++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the length of a stream. */
|
||||
unsigned long streamLength(const robj *subject) {
|
||||
stream *s = subject->ptr;
|
||||
return s->length;
|
||||
}
|
||||
|
||||
/* Generate the next stream item ID given the previous one. If the current
|
||||
* milliseconds Unix time is greater than the previous one, just use this
|
||||
* as time part and start with sequence part of zero. Otherwise we use the
|
||||
@@ -77,8 +98,8 @@ void streamNextID(streamID *last_id, streamID *new_id) {
|
||||
new_id->ms = ms;
|
||||
new_id->seq = 0;
|
||||
} else {
|
||||
new_id->ms = last_id->ms;
|
||||
new_id->seq = last_id->seq+1;
|
||||
*new_id = *last_id;
|
||||
streamIncrID(new_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,9 +194,19 @@ int streamCompareID(streamID *a, streamID *b) {
|
||||
* C_ERR if an ID was given via 'use_id', but adding it failed since the
|
||||
* current top ID is greater or equal. */
|
||||
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
||||
/* If an ID was given, check that it's greater than the last entry ID
|
||||
* or return an error. */
|
||||
if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR;
|
||||
|
||||
/* Generate the new entry ID. */
|
||||
streamID id;
|
||||
if (use_id)
|
||||
id = *use_id;
|
||||
else
|
||||
streamNextID(&s->last_id,&id);
|
||||
|
||||
/* Check that the new ID is greater than the last entry ID
|
||||
* 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;
|
||||
|
||||
/* Add the new entry. */
|
||||
raxIterator ri;
|
||||
@@ -192,13 +223,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
/* Generate the new entry ID. */
|
||||
streamID id;
|
||||
if (use_id)
|
||||
id = *use_id;
|
||||
else
|
||||
streamNextID(&s->last_id,&id);
|
||||
|
||||
/* We have to add the key into the radix tree in lexicographic order,
|
||||
* to do so we consider the ID as a single 128 bit number written in
|
||||
* big endian, so that the most significant bytes are the first ones. */
|
||||
@@ -242,17 +266,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* the current node is full. */
|
||||
if (lp != NULL) {
|
||||
if (server.stream_node_max_bytes &&
|
||||
lp_bytes > server.stream_node_max_bytes)
|
||||
lp_bytes >= server.stream_node_max_bytes)
|
||||
{
|
||||
lp = NULL;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
int64_t count = lpGetInteger(lpFirst(lp));
|
||||
if (count > server.stream_node_max_entries) lp = NULL;
|
||||
if (count >= server.stream_node_max_entries) lp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int flags = STREAM_ITEM_FLAG_NONE;
|
||||
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) {
|
||||
if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
|
||||
master_id = id;
|
||||
streamEncodeID(rax_key,&id);
|
||||
/* Create the listpack having the master entry ID and fields. */
|
||||
@@ -773,6 +797,16 @@ int streamDeleteItem(stream *s, streamID *id) {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/* Get the last valid (non-tombstone) streamID of 's'. */
|
||||
void streamLastValidID(stream *s, streamID *maxid)
|
||||
{
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,s,NULL,NULL,1);
|
||||
int64_t numfields;
|
||||
streamIteratorGetID(&si,maxid,&numfields);
|
||||
streamIteratorStop(&si);
|
||||
}
|
||||
|
||||
/* Emit a reply in the client output buffer by formatting a Stream ID
|
||||
* in the standard <ms>-<seq> format, using the simple string protocol
|
||||
* of REPL. */
|
||||
@@ -814,6 +848,11 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
argv[11] = createStringObject("JUSTID",6);
|
||||
argv[12] = createStringObject("LASTID",6);
|
||||
argv[13] = createObjectFromStreamID(&group->last_id);
|
||||
|
||||
/* We use progagate() because this code path is not always called from
|
||||
* the command execution context. Moreover this will just alter the
|
||||
* consumer group state, and we don't need MULTI/EXEC wrapping because
|
||||
* there is no message state cross-message atomicity required. */
|
||||
propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[3]);
|
||||
@@ -841,6 +880,11 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
|
||||
argv[2] = key;
|
||||
argv[3] = groupname;
|
||||
argv[4] = createObjectFromStreamID(&group->last_id);
|
||||
|
||||
/* We use progagate() because this code path is not always called from
|
||||
* the command execution context. Moreover this will just alter the
|
||||
* consumer group state, and we don't need MULTI/EXEC wrapping because
|
||||
* there is no message state cross-message atomicity required. */
|
||||
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
@@ -1216,12 +1260,27 @@ void xaddCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return ASAP if minimal ID (0-0) was given so we avoid possibly creating
|
||||
* a new stream and have streamAppendItem fail, leaving an empty key in the
|
||||
* database. */
|
||||
if (id_given && id.ms == 0 && id.seq == 0) {
|
||||
addReplyError(c,"The ID specified in XADD must be greater than 0-0");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup the stream at key. */
|
||||
robj *o;
|
||||
stream *s;
|
||||
if ((o = streamTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
s = o->ptr;
|
||||
|
||||
/* Return ASAP if the stream has reached the last possible ID */
|
||||
if (s->last_id.ms == UINT64_MAX && s->last_id.seq == UINT64_MAX) {
|
||||
addReplyError(c,"The stream has exhausted the last possible ID, "
|
||||
"unable to add more items");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Append using the low level function and return the ID. */
|
||||
if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
|
||||
&id, id_given ? &id : NULL)
|
||||
@@ -1485,20 +1544,23 @@ void xreadCommand(client *c) {
|
||||
{
|
||||
serve_synchronously = 1;
|
||||
serve_history = 1;
|
||||
} else {
|
||||
} else if (s->length) {
|
||||
/* We also want to serve a consumer in a consumer group
|
||||
* synchronously in case the group top item delivered is smaller
|
||||
* than what the stream has inside. */
|
||||
streamID *last = &groups[i]->last_id;
|
||||
if (s->length && (streamCompareID(&s->last_id, last) > 0)) {
|
||||
streamID maxid, *last = &groups[i]->last_id;
|
||||
streamLastValidID(s, &maxid);
|
||||
if (streamCompareID(&maxid, last) > 0) {
|
||||
serve_synchronously = 1;
|
||||
*gt = *last;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (s->length) {
|
||||
/* For consumers without a group, we serve synchronously if we can
|
||||
* actually provide at least one item from the stream. */
|
||||
if (s->length && (streamCompareID(&s->last_id, gt) > 0)) {
|
||||
streamID maxid;
|
||||
streamLastValidID(s, &maxid);
|
||||
if (streamCompareID(&maxid, gt) > 0) {
|
||||
serve_synchronously = 1;
|
||||
}
|
||||
}
|
||||
@@ -1510,7 +1572,7 @@ void xreadCommand(client *c) {
|
||||
* so start from the next ID, since we want only messages with
|
||||
* IDs greater than start. */
|
||||
streamID start = *gt;
|
||||
start.seq++; /* uint64_t can't overflow in this context. */
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Emit the two elements sub-array consisting of the name
|
||||
* of the stream and the data we extracted from it. */
|
||||
@@ -1518,7 +1580,8 @@ void xreadCommand(client *c) {
|
||||
addReplyBulk(c,c->argv[streams_arg+i]);
|
||||
streamConsumer *consumer = NULL;
|
||||
if (groups) consumer = streamLookupConsumer(groups[i],
|
||||
consumername->ptr,1);
|
||||
consumername->ptr,
|
||||
SLC_NONE);
|
||||
streamPropInfo spi = {c->argv[i+streams_arg],groupname};
|
||||
int flags = 0;
|
||||
if (noack) flags |= STREAM_RWR_NOACK;
|
||||
@@ -1651,7 +1714,9 @@ streamCG *streamLookupCG(stream *s, sds groupname) {
|
||||
* consumer does not exist it is automatically created as a side effect
|
||||
* of calling this function, otherwise its last seen time is updated and
|
||||
* the existing consumer reference returned. */
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
|
||||
streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags) {
|
||||
int create = !(flags & SLC_NOCREAT);
|
||||
int refresh = !(flags & SLC_NOREFRESH);
|
||||
streamConsumer *consumer = raxFind(cg->consumers,(unsigned char*)name,
|
||||
sdslen(name));
|
||||
if (consumer == raxNotFound) {
|
||||
@@ -1662,7 +1727,7 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
|
||||
raxInsert(cg->consumers,(unsigned char*)name,sdslen(name),
|
||||
consumer,NULL);
|
||||
}
|
||||
consumer->seen_time = mstime();
|
||||
if (refresh) consumer->seen_time = mstime();
|
||||
return consumer;
|
||||
}
|
||||
|
||||
@@ -1670,7 +1735,8 @@ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
|
||||
* may have pending messages: they are removed from the PEL, and the number
|
||||
* of pending messages "lost" is returned. */
|
||||
uint64_t streamDelConsumer(streamCG *cg, sds name) {
|
||||
streamConsumer *consumer = streamLookupConsumer(cg,name,0);
|
||||
streamConsumer *consumer =
|
||||
streamLookupConsumer(cg,name,SLC_NOCREAT|SLC_NOREFRESH);
|
||||
if (consumer == NULL) return 0;
|
||||
|
||||
uint64_t retval = raxSize(consumer->pel);
|
||||
@@ -1847,11 +1913,7 @@ void xsetidCommand(client *c) {
|
||||
* item, otherwise the fundamental ID monotonicity assumption is violated. */
|
||||
if (s->length > 0) {
|
||||
streamID maxid;
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,s,NULL,NULL,1);
|
||||
int64_t numfields;
|
||||
streamIteratorGetID(&si,&maxid,&numfields);
|
||||
streamIteratorStop(&si);
|
||||
streamLastValidID(s,&maxid);
|
||||
|
||||
if (streamCompareID(&id,&maxid) < 0) {
|
||||
addReplyError(c,"The ID specified in XSETID is smaller than the "
|
||||
@@ -2005,15 +2067,18 @@ void xpendingCommand(client *c) {
|
||||
}
|
||||
/* XPENDING <key> <group> <start> <stop> <count> [<consumer>] variant. */
|
||||
else {
|
||||
streamConsumer *consumer = consumername ?
|
||||
streamLookupConsumer(group,consumername->ptr,0):
|
||||
NULL;
|
||||
streamConsumer *consumer = NULL;
|
||||
if (consumername) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
consumername->ptr,
|
||||
SLC_NOCREAT|SLC_NOREFRESH);
|
||||
|
||||
/* If a consumer name was mentioned but it does not exist, we can
|
||||
* just return an empty array. */
|
||||
if (consumername && consumer == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
return;
|
||||
/* If a consumer name was mentioned but it does not exist, we can
|
||||
* just return an empty array. */
|
||||
if (consumer == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rax *pel = consumer ? consumer->pel : group->pel;
|
||||
@@ -2222,7 +2287,7 @@ void xclaimCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Do the actual claiming. */
|
||||
streamConsumer *consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
|
||||
streamConsumer *consumer = NULL;
|
||||
void *arraylenptr = addDeferredMultiBulkLength(c);
|
||||
size_t arraylen = 0;
|
||||
for (int j = 5; j <= last_id_arg; j++) {
|
||||
@@ -2274,9 +2339,11 @@ void xclaimCommand(client *c) {
|
||||
if (nack->consumer)
|
||||
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
|
||||
/* Update the consumer and idle time. */
|
||||
if (consumer == NULL)
|
||||
consumer = streamLookupConsumer(group,c->argv[3]->ptr,SLC_NONE);
|
||||
nack->consumer = consumer;
|
||||
nack->delivery_time = deliverytime;
|
||||
/* Set the delivery attempts counter if given, otherwise
|
||||
/* Set the delivery attempts counter if given, otherwise
|
||||
* autoincrement unless JUSTID option provided */
|
||||
if (retrycount >= 0) {
|
||||
nack->delivery_count = retrycount;
|
||||
|
||||
+2
-3
@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
|
||||
/* Optimize: 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)
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (sdslen(ele) > server.zset_max_ziplist_value)
|
||||
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
|
||||
sdslen(ele) > server.zset_max_ziplist_value)
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
if (newscore) *newscore = score;
|
||||
*flags |= ZADD_ADDED;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "5.0.5"
|
||||
#define REDIS_VERSION "5.0.10"
|
||||
|
||||
+7
-3
@@ -177,9 +177,6 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
size_t zmalloc_size(void *ptr) {
|
||||
void *realptr = (char*)ptr-PREFIX_SIZE;
|
||||
size_t size = *((size_t*)realptr);
|
||||
/* Assume at least that all the allocations are padded at sizeof(long) by
|
||||
* the underlying allocator. */
|
||||
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
|
||||
return size+PREFIX_SIZE;
|
||||
}
|
||||
size_t zmalloc_usable(void *ptr) {
|
||||
@@ -323,6 +320,13 @@ int zmalloc_get_allocator_info(size_t *allocated,
|
||||
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void set_jemalloc_bg_thread(int enable) {
|
||||
/* let jemalloc do purging asynchronously, required when there's no traffic
|
||||
* after flushdb */
|
||||
char val = !!enable;
|
||||
je_mallctl("background_thread", NULL, 0, &val, 1);
|
||||
}
|
||||
#else
|
||||
int zmalloc_get_allocator_info(size_t *allocated,
|
||||
size_t *active,
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Check basic transactions on a replica.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a primary with a replica" {
|
||||
create_cluster 1 1
|
||||
}
|
||||
|
||||
test "Cluster should start ok" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
set primary [Rn 0]
|
||||
set replica [Rn 1]
|
||||
|
||||
test "Cant read from replica without READONLY" {
|
||||
$primary SET a 1
|
||||
catch {$replica GET a} err
|
||||
assert {[string range $err 0 4] eq {MOVED}}
|
||||
}
|
||||
|
||||
test "Can read from replica after READONLY" {
|
||||
$replica READONLY
|
||||
assert {[$replica GET a] eq {1}}
|
||||
}
|
||||
|
||||
test "Can preform HSET primary and HGET from replica" {
|
||||
$primary HSET h a 1
|
||||
$primary HSET h b 2
|
||||
$primary HSET h c 3
|
||||
assert {[$replica HGET h a] eq {1}}
|
||||
assert {[$replica HGET h b] eq {2}}
|
||||
assert {[$replica HGET h c] eq {3}}
|
||||
}
|
||||
|
||||
# didn't cherry pick b120366d4 to 5.0 yet
|
||||
#test "Can MULTI-EXEC transaction of HGET operations from replica" {
|
||||
# $replica MULTI
|
||||
# assert {[$replica HGET h a] eq {QUEUED}}
|
||||
# assert {[$replica HGET h b] eq {QUEUED}}
|
||||
# assert {[$replica HGET h c] eq {QUEUED}}
|
||||
# assert {[$replica EXEC] eq {1 2 3}}
|
||||
#}
|
||||
|
||||
test "MULTI-EXEC with write operations is MOVED" {
|
||||
$replica MULTI
|
||||
catch {$replica HSET h b 4} err
|
||||
assert {[string range $err 0 4] eq {MOVED}}
|
||||
catch {$replica exec} err
|
||||
assert {[string range $err 0 8] eq {EXECABORT}}
|
||||
}
|
||||
|
||||
test "read-only blocking operations from replica" {
|
||||
set rd [redis_deferring_client redis 1]
|
||||
$rd readonly
|
||||
$rd read
|
||||
$rd XREAD BLOCK 0 STREAMS k 0
|
||||
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 1 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "client wasn't blocked"
|
||||
}
|
||||
|
||||
$primary XADD k * foo bar
|
||||
set res [$rd read]
|
||||
set res [lindex [lindex [lindex [lindex $res 0] 1] 0] 1]
|
||||
assert {$res eq {foo bar}}
|
||||
$rd close
|
||||
}
|
||||
+21
-2
@@ -334,10 +334,16 @@ proc S {n args} {
|
||||
[dict get $s link] {*}$args
|
||||
}
|
||||
|
||||
# Returns a Redis instance by index.
|
||||
# Example:
|
||||
# [Rn 0] info
|
||||
proc Rn {n} {
|
||||
return [dict get [lindex $::redis_instances $n] link]
|
||||
}
|
||||
|
||||
# Like R but to chat with Redis instances.
|
||||
proc R {n args} {
|
||||
set r [lindex $::redis_instances $n]
|
||||
[dict get $r link] {*}$args
|
||||
[Rn $n] {*}$args
|
||||
}
|
||||
|
||||
proc get_info_field {info field} {
|
||||
@@ -509,3 +515,16 @@ proc restart_instance {type id} {
|
||||
}
|
||||
}
|
||||
|
||||
proc redis_deferring_client {type id} {
|
||||
set port [get_instance_attrib $type $id port]
|
||||
set host [get_instance_attrib $type $id host]
|
||||
set client [redis $host $port 1]
|
||||
return $client
|
||||
}
|
||||
|
||||
proc redis_client {type id} {
|
||||
set port [get_instance_attrib $type $id port]
|
||||
set host [get_instance_attrib $type $id host]
|
||||
set client [redis $host $port 0]
|
||||
return $client
|
||||
}
|
||||
|
||||
@@ -13,12 +13,16 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: commandfilter.so
|
||||
all: commandfilter.so testrdb.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
|
||||
commandfilter.xo: ../../src/redismodule.h
|
||||
testrdb.xo: ../../src/redismodule.h
|
||||
|
||||
commandfilter.so: commandfilter.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testrdb.so: testrdb.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
#include "redismodule.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* Module configuration, save aux or not? */
|
||||
long long conf_aux_count = 0;
|
||||
|
||||
/* Registered type */
|
||||
RedisModuleType *testrdb_type = NULL;
|
||||
|
||||
/* Global values to store and persist to aux */
|
||||
RedisModuleString *before_str = NULL;
|
||||
RedisModuleString *after_str = NULL;
|
||||
|
||||
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
assert(count==1);
|
||||
assert(encver==1);
|
||||
RedisModuleString *str = RedisModule_LoadString(rdb);
|
||||
return str;
|
||||
}
|
||||
|
||||
void testrdb_type_save(RedisModuleIO *rdb, void *value) {
|
||||
RedisModuleString *str = (RedisModuleString*)value;
|
||||
RedisModule_SaveSigned(rdb, 1);
|
||||
RedisModule_SaveString(rdb, str);
|
||||
}
|
||||
|
||||
void testrdb_aux_save(RedisModuleIO *rdb, int when) {
|
||||
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
|
||||
if (conf_aux_count==0) assert(0);
|
||||
if (when == REDISMODULE_AUX_BEFORE_RDB) {
|
||||
if (before_str) {
|
||||
RedisModule_SaveSigned(rdb, 1);
|
||||
RedisModule_SaveString(rdb, before_str);
|
||||
} else {
|
||||
RedisModule_SaveSigned(rdb, 0);
|
||||
}
|
||||
} else {
|
||||
if (after_str) {
|
||||
RedisModule_SaveSigned(rdb, 1);
|
||||
RedisModule_SaveString(rdb, after_str);
|
||||
} else {
|
||||
RedisModule_SaveSigned(rdb, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
|
||||
assert(encver == 1);
|
||||
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
|
||||
if (conf_aux_count==0) assert(0);
|
||||
RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
|
||||
if (when == REDISMODULE_AUX_BEFORE_RDB) {
|
||||
if (before_str)
|
||||
RedisModule_FreeString(ctx, before_str);
|
||||
before_str = NULL;
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
if (count)
|
||||
before_str = RedisModule_LoadString(rdb);
|
||||
} else {
|
||||
if (after_str)
|
||||
RedisModule_FreeString(ctx, after_str);
|
||||
after_str = NULL;
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
if (count)
|
||||
after_str = RedisModule_LoadString(rdb);
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void testrdb_type_free(void *value) {
|
||||
RedisModule_FreeString(NULL, (RedisModuleString*)value);
|
||||
}
|
||||
|
||||
int testrdb_set_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
if (before_str)
|
||||
RedisModule_FreeString(ctx, before_str);
|
||||
before_str = argv[1];
|
||||
RedisModule_RetainString(ctx, argv[1]);
|
||||
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_get_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
if (argc != 1){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
if (before_str)
|
||||
RedisModule_ReplyWithString(ctx, before_str);
|
||||
else
|
||||
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_set_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
if (after_str)
|
||||
RedisModule_FreeString(ctx, after_str);
|
||||
after_str = argv[1];
|
||||
RedisModule_RetainString(ctx, argv[1]);
|
||||
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_get_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
if (argc != 1){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
if (after_str)
|
||||
RedisModule_ReplyWithString(ctx, after_str);
|
||||
else
|
||||
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_set_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 3){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
|
||||
if (str)
|
||||
RedisModule_FreeString(ctx, str);
|
||||
RedisModule_ModuleTypeSetValue(key, testrdb_type, argv[2]);
|
||||
RedisModule_RetainString(ctx, argv[2]);
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int testrdb_get_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2){
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithString(ctx, str);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (argc > 0)
|
||||
RedisModule_StringToLongLong(argv[0], &conf_aux_count);
|
||||
|
||||
if (conf_aux_count==0) {
|
||||
RedisModuleTypeMethods datatype_methods = {
|
||||
.version = 1,
|
||||
.rdb_load = testrdb_type_load,
|
||||
.rdb_save = testrdb_type_save,
|
||||
.aof_rewrite = NULL,
|
||||
.digest = NULL,
|
||||
.free = testrdb_type_free,
|
||||
};
|
||||
|
||||
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
||||
if (testrdb_type == NULL)
|
||||
return REDISMODULE_ERR;
|
||||
} else {
|
||||
RedisModuleTypeMethods datatype_methods = {
|
||||
.version = REDISMODULE_TYPE_METHOD_VERSION,
|
||||
.rdb_load = testrdb_type_load,
|
||||
.rdb_save = testrdb_type_save,
|
||||
.aof_rewrite = NULL,
|
||||
.digest = NULL,
|
||||
.free = testrdb_type_free,
|
||||
.aux_load = testrdb_aux_load,
|
||||
.aux_save = testrdb_aux_save,
|
||||
.aux_save_triggers = (conf_aux_count == 1 ?
|
||||
REDISMODULE_AUX_AFTER_RDB :
|
||||
REDISMODULE_AUX_BEFORE_RDB | REDISMODULE_AUX_AFTER_RDB)
|
||||
};
|
||||
|
||||
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
||||
if (testrdb_type == NULL)
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.set.before", testrdb_set_before,"deny-oom",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.get.before", testrdb_get_before,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.set.after", testrdb_set_after,"deny-oom",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.get.after", testrdb_get_after,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.set.key", testrdb_set_key,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"testrdb.get.key", testrdb_get_key,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
+12
-1
@@ -36,7 +36,18 @@ start_server {tags {"dump"}} {
|
||||
assert {$ttl >= 2900 && $ttl <= 3100}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
|
||||
test {RESTORE with ABSTTL in the past} {
|
||||
r set foo bar
|
||||
set encoded [r dump foo]
|
||||
set now [clock milliseconds]
|
||||
r debug set-active-expire 0
|
||||
r restore foo [expr $now-3000] $encoded absttl REPLACE
|
||||
catch {r debug object foo} e
|
||||
r debug set-active-expire 1
|
||||
set e
|
||||
} {ERR no such key}
|
||||
|
||||
test {RESTORE can set LRU} {
|
||||
r set foo bar
|
||||
set encoded [r dump foo]
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
set testmodule [file normalize tests/modules/testrdb.so]
|
||||
|
||||
proc restart_and_wait {} {
|
||||
catch {
|
||||
r debug restart
|
||||
}
|
||||
|
||||
# wait for the server to come back up
|
||||
set retry 50
|
||||
while {$retry} {
|
||||
if {[catch { r ping }]} {
|
||||
after 100
|
||||
} else {
|
||||
break
|
||||
}
|
||||
incr retry -1
|
||||
}
|
||||
}
|
||||
|
||||
tags "modules" {
|
||||
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||
test {modules are able to persist types} {
|
||||
r testrdb.set.key key1 value1
|
||||
assert_equal "value1" [r testrdb.get.key key1]
|
||||
r debug reload
|
||||
assert_equal "value1" [r testrdb.get.key key1]
|
||||
}
|
||||
|
||||
test {modules global are lost without aux} {
|
||||
r testrdb.set.before global1
|
||||
assert_equal "global1" [r testrdb.get.before]
|
||||
restart_and_wait
|
||||
assert_equal "" [r testrdb.get.before]
|
||||
}
|
||||
}
|
||||
|
||||
start_server [list overrides [list loadmodule "$testmodule 2"]] {
|
||||
test {modules are able to persist globals before and after} {
|
||||
r testrdb.set.before global1
|
||||
r testrdb.set.after global2
|
||||
assert_equal "global1" [r testrdb.get.before]
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
restart_and_wait
|
||||
assert_equal "global1" [r testrdb.get.before]
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
start_server [list overrides [list loadmodule "$testmodule 1"]] {
|
||||
test {modules are able to persist globals just after} {
|
||||
r testrdb.set.after global2
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
restart_and_wait
|
||||
assert_equal "global2" [r testrdb.get.after]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# TODO: test short read handling
|
||||
|
||||
}
|
||||
@@ -147,6 +147,20 @@ start_server {
|
||||
assert {[lindex $res 0 1 1] == {2-0 {field1 B}}}
|
||||
}
|
||||
|
||||
test {Blocking XREADGROUP will not reply with an empty array} {
|
||||
r del mystream
|
||||
r XGROUP CREATE mystream mygroup $ MKSTREAM
|
||||
r XADD mystream 666 f v
|
||||
set res [r XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">"]
|
||||
assert {[lindex $res 0 1 0] == {666-0 {f v}}}
|
||||
r XADD mystream 667 f2 v2
|
||||
r XDEL mystream 667
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREADGROUP GROUP mygroup Alice BLOCK 10 STREAMS mystream ">"
|
||||
after 20
|
||||
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {mystream {}}
|
||||
}
|
||||
|
||||
test {XCLAIM can claim PEL items from another consumer} {
|
||||
# Add 3 items into the stream, and create a consumer group
|
||||
r del mystream
|
||||
|
||||
@@ -79,6 +79,12 @@ start_server {
|
||||
assert {[streamCompareID $id2 $id3] == -1}
|
||||
}
|
||||
|
||||
test {XADD IDs correctly report an error when overflowing} {
|
||||
r DEL mystream
|
||||
r xadd mystream 18446744073709551615-18446744073709551615 a b
|
||||
assert_error ERR* {r xadd mystream * c d}
|
||||
}
|
||||
|
||||
test {XADD with MAXLEN option} {
|
||||
r DEL mystream
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
@@ -117,6 +123,12 @@ start_server {
|
||||
assert {[r xlen mystream] == $j}
|
||||
}
|
||||
|
||||
test {XADD with ID 0-0} {
|
||||
r DEL otherstream
|
||||
catch {r XADD otherstream 0-0 k v} err
|
||||
assert {[r EXISTS otherstream] == 0}
|
||||
}
|
||||
|
||||
test {XRANGE COUNT works as expected} {
|
||||
assert {[llength [r xrange mystream - + COUNT 10]] == 10}
|
||||
}
|
||||
@@ -179,6 +191,17 @@ start_server {
|
||||
assert {[lindex $res 0 1 0 1] eq {old abcd1234}}
|
||||
}
|
||||
|
||||
test {Blocking XREAD will not reply with an empty array} {
|
||||
r del s1
|
||||
r XADD s1 666 f v
|
||||
r XADD s1 667 f2 v2
|
||||
r XDEL s1 667
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREAD BLOCK 10 STREAMS s1 666
|
||||
after 20
|
||||
assert {[$rd read] == {}} ;# before the fix, client didn't even block, but was served synchronously with {s1 {}}
|
||||
}
|
||||
|
||||
test "XREAD: XADD + DEL should not awake client" {
|
||||
set rd [redis_deferring_client]
|
||||
r del s1
|
||||
@@ -316,6 +339,33 @@ start_server {
|
||||
|
||||
assert_equal [r xrevrange teststream2 1234567891245 -] {{1234567891240-0 {key1 value2}} {1234567891230-0 {key1 value1}}}
|
||||
}
|
||||
|
||||
test {XREAD streamID edge (no-blocking)} {
|
||||
r del x
|
||||
r XADD x 1-1 f v
|
||||
r XADD x 1-18446744073709551615 f v
|
||||
r XADD x 2-1 f v
|
||||
set res [r XREAD BLOCK 0 STREAMS x 1-18446744073709551615]
|
||||
assert {[lindex $res 0 1 0] == {2-1 {f v}}}
|
||||
}
|
||||
|
||||
test {XREAD streamID edge (blocking)} {
|
||||
r del x
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREAD BLOCK 0 STREAMS x 1-18446744073709551615
|
||||
r XADD x 1-1 f v
|
||||
r XADD x 1-18446744073709551615 f v
|
||||
r XADD x 2-1 f v
|
||||
set res [$rd read]
|
||||
assert {[lindex $res 0 1 0] == {2-1 {f v}}}
|
||||
}
|
||||
|
||||
test {XADD streamID edge} {
|
||||
r del x
|
||||
r XADD x 2577343934890-18446744073709551615 f v ;# we need the timestamp to be in the future
|
||||
r XADD x * f2 v2
|
||||
assert_equal [r XRANGE x - +] {{2577343934890-18446744073709551615 {f v}} {2577343934891-0 {f2 v2}}}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||
@@ -355,12 +405,12 @@ start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries
|
||||
r XADD mystream * xitem v
|
||||
}
|
||||
r XTRIM mystream MAXLEN ~ 85
|
||||
assert {[r xlen mystream] == 89}
|
||||
assert {[r xlen mystream] == 90}
|
||||
r config set stream-node-max-entries 1
|
||||
r debug loadaof
|
||||
r XADD mystream * xitem v
|
||||
incr j
|
||||
assert {[r xlen mystream] == 90}
|
||||
assert {[r xlen mystream] == 91}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user