Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c696aebdba | ||
|
|
1cac9b4bf2 | ||
|
|
f63c5c7bfe | ||
|
|
668661da25 | ||
|
|
3c2800e333 | ||
|
|
7ac7ffd560 | ||
|
|
cc10172176 | ||
|
|
72420b0d7a | ||
|
|
33a50d24a5 | ||
|
|
6a92836f40 | ||
|
|
f179f71e42 | ||
|
|
1825a4ec22 | ||
|
|
9d20fdb404 | ||
|
|
ded1980e0d | ||
|
|
c3df78c2a4 | ||
|
|
8d38ef20a3 | ||
|
|
9b7009b141 | ||
|
|
0580244217 | ||
|
|
d5194dafd2 | ||
|
|
8897c15406 | ||
|
|
6dd5bad49b | ||
|
|
8302610199 | ||
|
|
dc5edc7bbe | ||
|
|
5f29e2e220 | ||
|
|
e1839ab327 | ||
|
|
3b207b894a | ||
|
|
7e350b0931 | ||
|
|
91238a6031 | ||
|
|
ee2da67cc6 | ||
|
|
78022492d5 | ||
|
|
232dca7fbc | ||
|
|
7f98129a81 | ||
|
|
71265fe389 | ||
|
|
8115be6e10 | ||
|
|
90e7b5a97a | ||
|
|
43151baf61 | ||
|
|
d3c17c9d0f | ||
|
|
296bd0976c | ||
|
|
e08c9c154b | ||
|
|
c6b1252f1d | ||
|
|
ce54e2991c | ||
|
|
c9274498db | ||
|
|
635d8d83d2 | ||
|
|
7c23e534ce | ||
|
|
9ea8ec421d | ||
|
|
1b7407fadf | ||
|
|
3bbf9747a3 | ||
|
|
f72f4ea311 | ||
|
|
84bdd44039 | ||
|
|
ef1833b3f9 | ||
|
|
623afd5e4b | ||
|
|
12b5ff1095 | ||
|
|
254d897e9e | ||
|
|
7f79849caa | ||
|
|
3ef2c8316e | ||
|
|
57aea4632f | ||
|
|
ba5145b8e9 | ||
|
|
76c59f0e44 | ||
|
|
1c6367144b | ||
|
|
6a3fca4c67 | ||
|
|
9ec144ea30 | ||
|
|
d04b521150 | ||
|
|
516f1c7722 | ||
|
|
8db67a556b | ||
|
|
db3d626bac | ||
|
|
71439a07fd | ||
|
|
c8a26834fc | ||
|
|
a13ba75091 | ||
|
|
500e51171d | ||
|
|
7854daa140 | ||
|
|
d38d82af05 | ||
|
|
4d747bb850 | ||
|
|
8824b509b7 | ||
|
|
000b055b76 | ||
|
|
9b2a0d5497 | ||
|
|
134b258252 | ||
|
|
1293e2a565 | ||
|
|
3cc4f469bc | ||
|
|
f4edd2b924 | ||
|
|
cedcc54e4a | ||
|
|
26e98da20b | ||
|
|
0e91093902 | ||
|
|
67452e9136 | ||
|
|
30f666ef4a | ||
|
|
bc6c1c40db | ||
|
|
76419d8d5b | ||
|
|
72ba60699d | ||
|
|
2ca2175362 | ||
|
|
bd7ddd79dd | ||
|
|
d13bc1433e | ||
|
|
44c5bce0f4 | ||
|
|
f066e52659 | ||
|
|
232149662c | ||
|
|
80bccd7195 | ||
|
|
b4867130f4 | ||
|
|
2c6ee0f9b3 | ||
|
|
107e93e75a | ||
|
|
9edb893c7d | ||
|
|
c55254a5f4 | ||
|
|
dfd250132d | ||
|
|
392a2566b6 | ||
|
|
7602f69530 | ||
|
|
c4f3585e1f | ||
|
|
d037e98711 | ||
|
|
e00ab32454 | ||
|
|
94f64de35c | ||
|
|
752d636fa6 | ||
|
|
552091f990 | ||
|
|
2280f4f7c4 | ||
|
|
e084b8ccbf | ||
|
|
fa726e2af5 | ||
|
|
40244b10f0 | ||
|
|
beab31512e | ||
|
|
07ccb642b7 | ||
|
|
60fdaf072d | ||
|
|
48b31b0dce | ||
|
|
ef3ff40206 | ||
|
|
ee223fb8c3 | ||
|
|
41295e5595 | ||
|
|
0ed3970f2c | ||
|
|
605dddbbc0 | ||
|
|
1f43bf29a3 | ||
|
|
69f0c6788f | ||
|
|
bc53a3abb9 | ||
|
|
fefe546068 | ||
|
|
a8862972b6 | ||
|
|
5e86daf947 | ||
|
|
7c8cf5acdd | ||
|
|
39e9eda377 | ||
|
|
62485232e8 | ||
|
|
a54873092e |
+872
@@ -11,6 +11,878 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
================================================================================
|
||||
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
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency CRITICAL: This release fixes an important AOF fysnc bug
|
||||
and other less critical issues.
|
||||
|
||||
|
||||
Dear user,
|
||||
|
||||
Redis 5.0.5 fixes an important issue with AOF and adds multiple very useful
|
||||
modules APIs. Moreover smaller bugs in other parts of Redis are fixed in
|
||||
this release.
|
||||
|
||||
The AOF bug
|
||||
-----------
|
||||
|
||||
The AOF bug happens when the fsync policy is set to "everysec", which is the
|
||||
default: if the write load in the server drops immediately, the commands
|
||||
executed in the latest second may not be fsync-ed to disk as it should.
|
||||
This may lead to data loss in case the write load drops immediately and
|
||||
successively a server crash happens.
|
||||
|
||||
Other things in this release
|
||||
----------------------------
|
||||
|
||||
* Streams: a bug in the iterator could prevent certain items to be returned in
|
||||
range queries under specific conditions.
|
||||
* Memleak in bitfieldCommand fixed.
|
||||
* Modules API: Preserve client->id for blocked clients.
|
||||
* Fix memory leak when rewriting config file in case of write errors.
|
||||
* New modules API: RedisModule_GetKeyNameFromIO().
|
||||
* Fix non critical bugs in diskless replication.
|
||||
* New mdouels API: command filtering. See RedisModule_RegisterCommandFilter();
|
||||
* Tests improved to be more deterministic.
|
||||
* Fix a Redis Cluster bug, manual failover may abort because of the master
|
||||
sending PINGs to the replicas.
|
||||
|
||||
The following is the full list of commmits.
|
||||
|
||||
Regards,
|
||||
Salvatore
|
||||
|
||||
Christian Zeller in commit 1cac9b4b:
|
||||
Typo fixes in CONTRIBUTING
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit f63c5c7b:
|
||||
Update CONTRIBUTING with present info.
|
||||
1 file changed, 15 insertions(+), 5 deletions(-)
|
||||
|
||||
antirez in commit 668661da:
|
||||
Narrow the effects of PR #6029 to the exact state.
|
||||
1 file changed, 17 insertions(+), 5 deletions(-)
|
||||
|
||||
chendianqiang in commit 3c2800e3:
|
||||
stop ping when client pause
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 7ac7ffd5:
|
||||
Test: fix slowlog test false positive.
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit cc101721:
|
||||
Make comment in getClientOutputBufferMemoryUsage() describing the present.
|
||||
1 file changed, 1 insertion(+), 8 deletions(-)
|
||||
|
||||
WuYunlong in commit 72420b0d:
|
||||
Do not active expire keys in the background when the switch is off.
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
liaotonglang in commit 33a50d24:
|
||||
delete sdsTest() from REDIS_TEST
|
||||
1 file changed, 2 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit 6a92836f:
|
||||
test cases: skiptill -> skip-till
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Oran Agra in commit f179f71e:
|
||||
make replication tests more stable on slow machines
|
||||
3 files changed, 34 insertions(+), 4 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 1825a4ec:
|
||||
Add runtest-moduleapi with commandfilter coverage.
|
||||
5 files changed, 63 insertions(+), 28 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 9d20fdb4:
|
||||
fix: missing initialization.
|
||||
3 files changed, 1 insertion(+)
|
||||
|
||||
antirez in commit ded1980e:
|
||||
Test: disable module testing for now.
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
Yossi Gottlieb in commit c3df78c2:
|
||||
CommandFilter API: REDISMODULE_CMDFILTER_NOSELF.
|
||||
4 files changed, 62 insertions(+), 15 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 8d38ef20:
|
||||
CommandFilter API: fix UnregisterCommandFilter.
|
||||
1 file changed, 2 insertions(+), 3 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 9b7009b1:
|
||||
CommandFilter API: Add unregister option.
|
||||
4 files changed, 126 insertions(+), 32 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 05802442:
|
||||
CommandFilter API: Extend documentation.
|
||||
1 file changed, 43 insertions(+), 5 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit d5194daf:
|
||||
CommandFilter API: hellofilter and tests.
|
||||
2 files changed, 47 insertions(+), 5 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 8897c154:
|
||||
CommandFilter API: Support Lua and RM_call() flows.
|
||||
2 files changed, 18 insertions(+), 7 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 6dd5bad4:
|
||||
CommandFilter API: More cleanup.
|
||||
2 files changed, 10 insertions(+), 29 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 83026101:
|
||||
Add command filter Module API tests.
|
||||
2 files changed, 28 insertions(+)
|
||||
|
||||
Yossi Gottlieb in commit dc5edc7b:
|
||||
Add command filtering argument handling API.
|
||||
3 files changed, 132 insertions(+), 13 deletions(-)
|
||||
|
||||
Yossi Gottlieb in commit 5f29e2e2:
|
||||
Initial command filter experiment.
|
||||
6 files changed, 161 insertions(+), 2 deletions(-)
|
||||
|
||||
Oran Agra in commit e1839ab3:
|
||||
diskless fork kept streaming RDB to a disconnected slave
|
||||
1 file changed, 10 insertions(+)
|
||||
|
||||
Oran Agra in commit 3b207b89:
|
||||
diskless replication - notify slave when rdb transfer failed
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
antirez in commit 7e350b09:
|
||||
More sensible name for function: restartAOFAfterSYNC().
|
||||
1 file changed, 7 insertions(+), 3 deletions(-)
|
||||
|
||||
antirez in commit 91238a60:
|
||||
Mostly aesthetic changes to restartAOF().
|
||||
1 file changed, 7 insertions(+), 3 deletions(-)
|
||||
|
||||
oranagra in commit ee2da67c:
|
||||
bugfix to restartAOF, exit will never happen since retry will get negative.
|
||||
1 file changed, 5 insertions(+), 4 deletions(-)
|
||||
|
||||
Oran Agra in commit 78022492:
|
||||
Add log when server dies of SIGTERM during loading
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Yossi Gottlieb in commit 232dca7f:
|
||||
Add RedisModule_GetKeyNameFromIO().
|
||||
8 files changed, 30 insertions(+), 17 deletions(-)
|
||||
|
||||
antirez in commit 7f98129a:
|
||||
MANIFESTO: simplicity and lock-in.
|
||||
1 file changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 71265fe3:
|
||||
MANIFESTO v2.
|
||||
1 file changed, 41 insertions(+), 6 deletions(-)
|
||||
|
||||
yongman in commit 8115be6e:
|
||||
Fix uint64_t hash value in active defrag
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
Angus Pearson in commit 90e7b5a9:
|
||||
Enlarge error buffer in redis-check-aof.c to remove compiler warning of output truncation through snprintf format string
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
zhaozhao.zz in commit 43151baf:
|
||||
fix memory leak when rewrite config file
|
||||
1 file changed, 3 insertions(+), 4 deletions(-)
|
||||
|
||||
唐权 in commit d3c17c9d:
|
||||
Update ziplist.c
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
stan011 in commit 296bd097:
|
||||
change the comments there may have a mis type
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Yossi Gottlieb in commit e08c9c15:
|
||||
Preserve client->id for blocked clients.
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
zhaozhao.zz in commit c6b1252f:
|
||||
aof: enhance AOF_FSYNC_EVERYSEC, more details in #5985
|
||||
2 files changed, 32 insertions(+), 3 deletions(-)
|
||||
|
||||
David Carlier in commit ce54e299:
|
||||
build fix
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
yongman in commit c9274498:
|
||||
Fix memleak in bitfieldCommand
|
||||
1 file changed, 8 insertions(+), 2 deletions(-)
|
||||
|
||||
James Rouzier in commit 635d8d83:
|
||||
Fix start and end key initialize
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Salvatore Sanfilippo in commit 7c23e534:
|
||||
Merge pull request #6047 from abhaynahar/removed-obsolete-warning-5.0
|
||||
abhay in commit 9ea8ec42:
|
||||
removed obsolete warning as per - https://github.com/antirez/redis/issues/5291
|
||||
1 file changed, 1 insertion(+), 7 deletions(-)
|
||||
|
||||
antirez in commit 1b7407fa:
|
||||
Aesthetic change to #5962 to conform to Redis style.
|
||||
1 file changed, 1 insertion(+), 3 deletions(-)
|
||||
|
||||
Oran Agra in commit 3bbf9747:
|
||||
slave corrupts replication stream when module blocked client uses large reply (or POSTPONED_ARRAY)
|
||||
3 files changed, 15 insertions(+), 6 deletions(-)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.4 Released Mon Mar 18 17:12:53 CET 2019
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency HIGH: This release fixes several Redis stability issues.
|
||||
|
||||
Dear Redis users, this release includes a number of fixes for bugs that may
|
||||
result in Redis crashing in special conditions (not normal usage, but specific
|
||||
artificial conditions), fixes to certain Redis behaviors especially around
|
||||
Redis streams, and finally a set of new APIs for Redis Modules.
|
||||
|
||||
Specifically:
|
||||
|
||||
* Hyperloglog different coding errors leading to potential crashes were fixed.
|
||||
* A replication bug leading to a potential crash in case of plain misuse of handshake commands was fixed.
|
||||
* XCLAIM command incrementing of number of deliveries was fixed.
|
||||
* LFU field management in objects was improved.
|
||||
* A potential overflow in the redis-check-aof was fixed.
|
||||
* A memory leak in case of API misuse was fixed.
|
||||
* ZPOP* behavior when count is 0 is fixed.
|
||||
* A few redis-cli --cluster bugs were fixed, plus a few improvements.
|
||||
* Many other smaller bugs.
|
||||
|
||||
We suggest to upgrade Redis, especially in case your instance is facing
|
||||
untrusted users (for instance Cloud providers) because several of these
|
||||
bugs could result in unwanted crashes.
|
||||
|
||||
This is the list of commits:
|
||||
|
||||
antirez in commit 84bdd440:
|
||||
HyperLogLog: fix comment in hllCount().
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit ef1833b3:
|
||||
HyperLogLog: handle wrong offset in the base case.
|
||||
1 file changed, 2 insertions(+), 6 deletions(-)
|
||||
|
||||
antirez in commit 623afd5e:
|
||||
HyperLogLog: speedup fuzz test.
|
||||
1 file changed, 1 insertion(+), 2 deletions(-)
|
||||
|
||||
antirez in commit 12b5ff10:
|
||||
HyperLogLog: enlarge reghisto variable for safety.
|
||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 254d897e:
|
||||
HyperLogLog: dense/sparse repr parsing fuzz test.
|
||||
1 file changed, 29 insertions(+)
|
||||
|
||||
John Sully in commit 7f79849c:
|
||||
Fix hyperloglog corruption
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
Brad Solomon in commit 3ef2c831:
|
||||
Provide an uninstall target in Makefile
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
antirez in commit 57aea463:
|
||||
redis-check-aof: fix potential overflow.
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit ba5145b8:
|
||||
Fix objectSetLRUOrLFU() when LFU underflows.
|
||||
1 file changed, 11 insertions(+), 7 deletions(-)
|
||||
|
||||
antirez in commit 76c59f0e:
|
||||
Fix ZPOP return type when COUNT=0. Related to #5799.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 1c636714:
|
||||
Improve comments after merging #5834.
|
||||
2 files changed, 14 insertions(+), 8 deletions(-)
|
||||
|
||||
Guy Benoish in commit 6a3fca4c:
|
||||
Trim SDS free space of retained module strings
|
||||
4 files changed, 28 insertions(+), 5 deletions(-)
|
||||
|
||||
Guy Benoish in commit 9ec144ea:
|
||||
Fix mismatching keyspace notification classes
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Guy Benoish in commit d04b5211:
|
||||
Fix zlexrangespec mem-leak in genericZrangebylexCommand
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit 516f1c77:
|
||||
Use memtoll() in 'CONFIG SET client-output-buffer-limit'
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
Guy Benoish in commit 8db67a55:
|
||||
Increase string2ld's buffer size (and fix HINCRBYFLOAT)
|
||||
2 files changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
Guy Benoish in commit db3d626b:
|
||||
Check server.verbosity in RM_LogRaw
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
Guy Benoish in commit 71439a07:
|
||||
ZPOP should return an empty array if COUNT=0
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit c8a26834:
|
||||
Modules shared API: export new core APIs.
|
||||
2 files changed, 6 insertions(+)
|
||||
|
||||
antirez in commit a13ba750:
|
||||
Modules shared API: also unregister the module as user.
|
||||
1 file changed, 23 insertions(+)
|
||||
|
||||
antirez in commit 500e5117:
|
||||
Modules shared API: prevent unloading of used modules.
|
||||
1 file changed, 10 insertions(+), 4 deletions(-)
|
||||
|
||||
antirez in commit 7854daa1:
|
||||
Modules shared API: unregister APIs function.
|
||||
1 file changed, 25 insertions(+)
|
||||
|
||||
antirez in commit d38d82af:
|
||||
Modules shared API: initial core functions.
|
||||
2 files changed, 89 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 4d747bb8:
|
||||
Revert shared APIs to modify the design.
|
||||
3 files changed, 120 deletions(-)
|
||||
|
||||
MeirShpilraien in commit 8824b509:
|
||||
added module ability to register api to be used by other modules
|
||||
3 files changed, 120 insertions(+)
|
||||
|
||||
zhaozhao.zz in commit 000b055b:
|
||||
Streams: checkType before XGROUP CREATE
|
||||
1 file changed, 7 insertions(+), 5 deletions(-)
|
||||
|
||||
antirez in commit 9b2a0d54:
|
||||
Fix BZPOP arity, backport from fix in cd2743c.
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
chendianqiang in commit 134b2582:
|
||||
optimize cluster failover
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
Steve Webster in commit 1293e2a5:
|
||||
Only increment delivery count if JUSTID option is omitted
|
||||
2 files changed, 18 insertions(+), 3 deletions(-)
|
||||
|
||||
Steve Webster in commit 3cc4f469:
|
||||
Increment delivery counter on XCLAIM unless RETRYCOUNT specified
|
||||
2 files changed, 35 insertions(+), 2 deletions(-)
|
||||
|
||||
antirez in commit f4edd2b9:
|
||||
Merge branch '5.0' of github.com:/antirez/redis into 5.0
|
||||
swilly22 in commit cedcc54e:
|
||||
document additional flag of RM_GetContextFlags
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
swilly22 in commit 26e98da2:
|
||||
Extend REDISMODULE_CTX_FLAGS to indicate if command was sent by master
|
||||
2 files changed, 6 insertions(+)
|
||||
|
||||
Salvatore Sanfilippo in commit 0e910939:
|
||||
Merge pull request #5879 from meierfra-ergon/redis-cli-assume-yes
|
||||
antirez in commit 67452e91:
|
||||
Make comment in #5911 stay inside 80 cols.
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
John Sully in commit 30f666ef:
|
||||
Replicas aren't allowed to run the replicaof command
|
||||
1 file changed, 8 insertions(+)
|
||||
|
||||
Frank Meier in commit bc6c1c40:
|
||||
extend use of cluster-yes option to other confimation questions
|
||||
1 file changed, 9 insertions(+)
|
||||
|
||||
antirez in commit 76419d8d:
|
||||
Merge branch '5.0' of github.com:/antirez/redis into 5.0
|
||||
Oran Agra in commit 72ba6069:
|
||||
redis-cli add support for --memkeys, fix --bigkeys for module types
|
||||
1 file changed, 132 insertions(+), 81 deletions(-)
|
||||
|
||||
chendianqiang in commit 2ca21753:
|
||||
fix replicationid will not change for server.masterhost==NULL in cluster mode when restart slave
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Salvatore Sanfilippo in commit bd7ddd79:
|
||||
Merge pull request #5870 from fengweiyuan/5.0
|
||||
varianfeng in commit d13bc143:
|
||||
fix corrupt_rdb.c bug.Let the name of input rdb file name be valid.
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
artix in commit 44c5bce0:
|
||||
Cluster Manager: fix replica assigment anti-affinity (create)
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
artix in commit f066e526:
|
||||
Cluster Manager: remove unused code elements
|
||||
1 file changed, 8 insertions(+), 13 deletions(-)
|
||||
|
||||
Zhicheng Wei in commit 23214966:
|
||||
fix clusterManagerGetAntiAffinityScore double free otypes
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 80bccd71:
|
||||
Remove debugging printf from replication.tcl test.
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.3 Released Tue Dec 11 18:17:26 CET 2018
|
||||
================================================================================
|
||||
|
||||
Upgrade urgency HIGH: Redis 5 is consolidating, upgrading is a good idea.
|
||||
However there is nothing very critical here, but certain
|
||||
issues resolved could lead to very rare crashes.
|
||||
|
||||
Welcome to Redis 5.0.3, several interesting bug fixes here:
|
||||
|
||||
* Redis no longer panics when you send data to a replica-mode connection that
|
||||
is in MONITOR or SYNC mode.
|
||||
|
||||
* Fixes to certain sorted set edge cases. You are unlikely to ever notice those
|
||||
issues, but now it is more correct.
|
||||
|
||||
* Certain BSD variants now are better supported: build & register logging
|
||||
on crash.
|
||||
|
||||
* The networking core now recovers if an IPv6 address is listed in bind but
|
||||
is actually not able to work because there is no such protocol in the
|
||||
system.
|
||||
|
||||
* redis-cli cluster mode improved in many ways. Especially the fix subcommand
|
||||
work was enhanced to cover other edge cases that were still not covered
|
||||
after the work done for Redis 5.
|
||||
|
||||
* MEMORY USAGE is now more accurate.
|
||||
|
||||
* DEBUG DIGEST-VALUE added in case you want to make sure a given set of keys
|
||||
(and not the whole DB) are excatly the same between two instances.
|
||||
|
||||
* Fix a potential crash in the networking code related to recent changes
|
||||
to the way the reply is consumed.
|
||||
|
||||
* Reject EXEC containing write commands against an instance that changed role
|
||||
from master to replica during our transaction.
|
||||
|
||||
* Fix a crash in KEYS and other commands using pattern matching, in an edge
|
||||
case where the pattern contains a zero byte.
|
||||
|
||||
* Fix eviction during AOF loading due to maxmemory triggered by commands
|
||||
executed in loading state.
|
||||
|
||||
The following is the list of commmits if you want to check credits or dig
|
||||
further in the details.
|
||||
|
||||
commit 2c6ee0f9b3d9ca48c6da8bd18796186784216bff
|
||||
Author: antirez <antirez@gmail.com>
|
||||
Date: Wed Dec 12 11:37:15 2018 +0100
|
||||
|
||||
freeMemoryIfNeeded() small refactoring.
|
||||
|
||||
Related to issue #5686 and PR #5689.
|
||||
|
||||
commit 107e93e75acfd5def0252efb6870751940816395
|
||||
Author: zhaozhao.zz <zhaozhao.zz@alibaba-inc.com>
|
||||
Date: Wed Dec 12 00:25:24 2018 +0800
|
||||
|
||||
evict: don't care about mem if loading
|
||||
|
||||
When loading data, we call processEventsWhileBlocked
|
||||
to process events and execute commands.
|
||||
But if we are loading AOF it's dangerous, because
|
||||
processCommand would call freeMemoryIfNeeded to evict,
|
||||
and that will break data consistency, see issue #5686.
|
||||
|
||||
antirez in commit ee93dc0b:
|
||||
Crashing is too much in addReplyErrorLength().
|
||||
1 file changed, 6 deletions(-)
|
||||
|
||||
hdmg in commit c55254a5:
|
||||
fix comments fault discription
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
lsytj0413 in commit dfd25013:
|
||||
fix a typo: craeted -> created
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 392a2566:
|
||||
stringmatchlen() fuzz test added.
|
||||
3 files changed, 22 insertions(+)
|
||||
|
||||
antirez in commit 7602f695:
|
||||
Fix stringmatchlen() read past buffer bug.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
zhaozhao.zz in commit c4f3585e:
|
||||
multi: ignore multiState's cmd_flags when loading AOF
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit d037e987:
|
||||
Reject EXEC containing write commands against RO replica.
|
||||
2 files changed, 20 insertions(+)
|
||||
|
||||
artix in commit e00ab324:
|
||||
Cluster Manager: - Multiple owners checking in 'fix'/'check' commands is
|
||||
now optional (using --cluster-search-multiple-owners). - Updated help.
|
||||
1 file changed, 14 insertions(+), 5 deletions(-)
|
||||
|
||||
artix in commit 94f64de3:
|
||||
Cluster Manager: FixOpenSlot now correctly updates in-memory cluster
|
||||
configuration. Improved output messages.
|
||||
1 file changed, 17 insertions(+), 5 deletions(-)
|
||||
|
||||
artix in commit 752d636f:
|
||||
Cluster Manager: 'fix' command now handles open slots with migrating state
|
||||
in one node and importing state in multiple nodes.
|
||||
1 file changed, 74 insertions(+), 6 deletions(-)
|
||||
|
||||
artix in commit 552091f9:
|
||||
Cluster Manager: setting new slot owner is now handled atomically in
|
||||
'fix' command.
|
||||
1 file changed, 72 insertions(+), 31 deletions(-)
|
||||
|
||||
artix in commit 2280f4f7:
|
||||
Cluster Manager: code cleanup.
|
||||
1 file changed, 41 insertions(+), 87 deletions(-)
|
||||
|
||||
artix in commit e084b8cc:
|
||||
Cluster Manager: check/fix commands now handle multiple owners even
|
||||
if all slots are covered and not open.
|
||||
1 file changed, 129 insertions(+), 6 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit fa726e2a:
|
||||
remove useless tryObjectEncoding in debug assert
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
Oran Agra in commit 40244b10:
|
||||
fix #5580, display fragmentation and rss overhead bytes as signed
|
||||
2 files changed, 6 insertions(+), 6 deletions(-)
|
||||
|
||||
zhaozhao.zz in commit beab3151:
|
||||
networking: current_client should not be NULL when trim qb_pos
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
antirez in commit 07ccb642:
|
||||
Remove no longer relevant comment in processCommand().
|
||||
1 file changed, 2 insertions(+), 6 deletions(-)
|
||||
|
||||
antirez in commit 60fdaf07:
|
||||
DEBUG DIGEST-VALUE implemented.
|
||||
1 file changed, 17 insertions(+), 3 deletions(-)
|
||||
|
||||
antirez in commit 48b31b0d:
|
||||
DEBUG DIGEST refactoring: extract function to digest a value.
|
||||
1 file changed, 142 insertions(+), 131 deletions(-)
|
||||
|
||||
yura in commit ef3ff402:
|
||||
redis-cli reshard/rebalance: ability to force replacement on existing keys
|
||||
1 file changed, 6 insertions(+), 5 deletions(-)
|
||||
|
||||
Thomas Orozco in commit ee223fb8:
|
||||
cli: pass auth through REDISCLI_AUTH
|
||||
1 file changed, 14 insertions(+)
|
||||
|
||||
yongman in commit 41295e55:
|
||||
Fix cluster call reply format readable
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Oran Agra in commit 0ed3970f:
|
||||
fix small test suite race conditions
|
||||
3 files changed, 11 insertions(+)
|
||||
|
||||
zhaozhao.zz in commit 605dddbb:
|
||||
MEMORY command: make USAGE more accurate
|
||||
1 file changed, 7 insertions(+), 6 deletions(-)
|
||||
|
||||
yongman in commit 1f43bf29:
|
||||
Fix choose a random master node for slot assignment
|
||||
1 file changed, 29 insertions(+), 5 deletions(-)
|
||||
|
||||
Weiliang Li in commit 69f0c678:
|
||||
fix comment typo in util.c
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Chris Lamb in commit bc53a3ab:
|
||||
Clarify the "Creating Server TCP listening socket" error.
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
Chris Lamb in commit fefe5460:
|
||||
Don't treat unsupported protocols as fatal errors
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
David Carlier in commit a8862972:
|
||||
OpenBSD support.
|
||||
3 files changed, 74 insertions(+), 1 deletion(-)
|
||||
|
||||
David Carlier in commit 5e86daf9:
|
||||
Backtrace/register dump on BSD.
|
||||
3 files changed, 97 insertions(+), 3 deletions(-)
|
||||
|
||||
Guy Benoish in commit 7c8cf5ac:
|
||||
Don't call sdscmp() with shared.maxstring or shared.minstring
|
||||
2 files changed, 23 insertions(+), 9 deletions(-)
|
||||
|
||||
Qu Chen in commit 39e9eda3:
|
||||
Add unit test for stream XCLAIM command.
|
||||
1 file changed, 48 insertions(+)
|
||||
|
||||
antirez in commit 62485232:
|
||||
Abort instead of crashing when loading bad stream master key.
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
Madelyn Olson in commit a5487309:
|
||||
Fixed a serverPanic when sending an invalid command to a monitor client
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
================================================================================
|
||||
Redis 5.0.2 Released Thu Nov 22 11:22:37 CET 2018
|
||||
================================================================================
|
||||
|
||||
+15
-5
@@ -14,9 +14,7 @@ each source file that you contribute.
|
||||
|
||||
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
|
||||
bugs in the Github issues system. We'll be very happy to help you and provide
|
||||
all the support at the Reddit sub:
|
||||
|
||||
http://reddit.com/r/redis
|
||||
all the support in the mailing list.
|
||||
|
||||
There is also an active community of Redis users at Stack Overflow:
|
||||
|
||||
@@ -24,7 +22,12 @@ each source file that you contribute.
|
||||
|
||||
# How to provide a patch for a new feature
|
||||
|
||||
1. If it is a major feature or a semantical change, please post it as a new submission in r/redis on Reddit at http://reddit.com/r/redis. Try to be passionate about why the feature is needed, make users upvote your proposal to gain traction and so forth. Read feedbacks about the community. But in this first step **please don't write code yet**.
|
||||
1. If it is a major feature or a semantical change, please don't start coding
|
||||
straight away: if your feature is not a conceptual fit you'll lose a lot of
|
||||
time writing the code without any reason. Start by posting in the mailing list
|
||||
and creating an issue at Github with the description of, exactly, what you want
|
||||
to accomplish and why. Use cases are important for features to be accepted.
|
||||
Here you'll see if there is consensus about your idea.
|
||||
|
||||
2. If in step 1 you get an acknowledgment from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
@@ -35,6 +38,13 @@ each source file that you contribute.
|
||||
d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
|
||||
e. Done :)
|
||||
|
||||
For minor fixes just open a pull request on Github.
|
||||
3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait
|
||||
for a *very* long time. However this is not lack of interest, as the project
|
||||
gets more and more users, we find ourselves in a constant need to prioritize
|
||||
certain issues/PRs over others. If you think your issue/PR is very important
|
||||
try to popularize it, have other users commenting and sharing their point of
|
||||
view and so forth. This helps.
|
||||
|
||||
4. For minor fixes just open a pull request on Github.
|
||||
|
||||
Thanks!
|
||||
|
||||
@@ -34,7 +34,21 @@ Redis Manifesto
|
||||
so that the complexity is obvious and more complex operations can be
|
||||
performed as the sum of the basic operations.
|
||||
|
||||
4 - Code is like a poem; it's not just something we write to reach some
|
||||
4 - We believe in code efficiency. Computers get faster and faster, yet we
|
||||
believe that abusing computing capabilities is not wise: the amount of
|
||||
operations you can do for a given amount of energy remains anyway a
|
||||
significant parameter: it allows to do more with less computers and, at
|
||||
the same time, having a smaller environmental impact. Similarly Redis is
|
||||
able to "scale down" to smaller devices. It is perfectly usable in a
|
||||
Raspberry Pi and other small ARM based computers. Faster code having
|
||||
just the layers of abstractions that are really needed will also result,
|
||||
often, in more predictable performances. We think likewise about memory
|
||||
usage, one of the fundamental goals of the Redis project is to
|
||||
incrementally build more and more memory efficient data structures, so that
|
||||
problems that were not approachable in RAM in the past will be perfectly
|
||||
fine to handle in the future.
|
||||
|
||||
5 - Code is like a poem; it's not just something we write to reach some
|
||||
practical result. Sometimes people that are far from the Redis philosophy
|
||||
suggest using other code written by other authors (frequently in other
|
||||
languages) in order to implement something Redis currently lacks. But to us
|
||||
@@ -45,23 +59,48 @@ Redis Manifesto
|
||||
when needed. At the same time, when writing the Redis story we're trying to
|
||||
write smaller stories that will fit in to other code.
|
||||
|
||||
5 - We're against complexity. We believe designing systems is a fight against
|
||||
6 - We're against complexity. We believe designing systems is a fight against
|
||||
complexity. We'll accept to fight the complexity when it's worthwhile but
|
||||
we'll try hard to recognize when a small feature is not worth 1000s of lines
|
||||
of code. Most of the time the best way to fight complexity is by not
|
||||
creating it at all.
|
||||
creating it at all. Complexity is also a form of lock-in: code that is
|
||||
very hard to understand cannot be modified by users in an independent way
|
||||
regardless of the license. One of the main Redis goals is to remain
|
||||
understandable, enough for a single programmer to have a clear idea of how
|
||||
it works in detail just reading the source code for a couple of weeks.
|
||||
|
||||
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
7 - Threading is not a silver bullet. Instead of making Redis threaded we
|
||||
believe on the idea of an efficient (mostly) single threaded Redis core.
|
||||
Multiple of such cores, that may run in the same computer or may run
|
||||
in multiple computers, are abstracted away as a single big system by
|
||||
higher order protocols and features: Redis Cluster and the upcoming
|
||||
Redis Proxy are our main goals. A shared nothing approach is not just
|
||||
much simpler (see the previous point in this document), is also optimal
|
||||
in NUMA systems. In the specific case of Redis it allows for each instance
|
||||
to have a more limited amount of data, making the Redis persist-by-fork
|
||||
approach more sounding. In the future we may explore parallelism only for
|
||||
I/O, which is the low hanging fruit: minimal complexity could provide an
|
||||
improved single process experience.
|
||||
|
||||
8 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
naturally into a distributed version of Redis and 2) a more complex API that
|
||||
supports multi-key operations. Both are useful if used judiciously but
|
||||
there's no way to make the more complex multi-keys API distributed in an
|
||||
opaque way without violating our other principles. We don't want to provide
|
||||
the illusion of something that will work magically when actually it can't in
|
||||
all cases. Instead we'll provide commands to quickly migrate keys from one
|
||||
instance to another to perform multi-key operations and expose the tradeoffs
|
||||
to the user.
|
||||
instance to another to perform multi-key operations and expose the
|
||||
trade-offs to the user.
|
||||
|
||||
7 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
9 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
only way it can be worth is by enjoying it. When there is no longer joy in
|
||||
writing code, the best thing to do is stop. To prevent this, we'll avoid
|
||||
taking paths that will make Redis less of a joy to develop.
|
||||
|
||||
10 - All the above points are put together in what we call opportunistic
|
||||
programming: trying to get the most for the user with minimal increases
|
||||
in complexity (hanging fruits). Solve 95% of the problem with 5% of the
|
||||
code when it is acceptable. Avoid a fixed schedule but follow the flow of
|
||||
user requests, inspiration, Redis internal readiness for certain features
|
||||
(sometimes many past changes reach a critical point making a previously
|
||||
complex feature very easy to obtain).
|
||||
|
||||
+1
-7
@@ -824,13 +824,7 @@ aof-use-rdb-preamble yes
|
||||
lua-time-limit 5000
|
||||
|
||||
################################ REDIS CLUSTER ###############################
|
||||
#
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however
|
||||
# in order to mark it as "mature" we need to wait for a non trivial percentage
|
||||
# of users to deploy it in production.
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
#
|
||||
|
||||
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
|
||||
# started as cluster nodes can. In order to start a Redis instance as a
|
||||
# cluster node enable the cluster support uncommenting the following:
|
||||
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
||||
+11
-2
@@ -102,14 +102,20 @@ else
|
||||
ifeq ($(uname_S),OpenBSD)
|
||||
# OpenBSD
|
||||
FINAL_LIBS+= -lpthread
|
||||
ifeq ($(USE_BACKTRACE),yes)
|
||||
FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
|
||||
FINAL_LDFLAGS+= -L/usr/local/lib
|
||||
FINAL_LIBS+= -lexecinfo
|
||||
endif
|
||||
|
||||
else
|
||||
ifeq ($(uname_S),FreeBSD)
|
||||
# FreeBSD
|
||||
FINAL_LIBS+= -lpthread
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),DragonFly)
|
||||
# FreeBSD
|
||||
FINAL_LIBS+= -lpthread
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
@@ -304,3 +310,6 @@ install: all
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||
|
||||
uninstall:
|
||||
rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)}
|
||||
|
||||
@@ -197,6 +197,12 @@ ssize_t aofRewriteBufferWrite(int fd) {
|
||||
* AOF file implementation
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Return true if an AOf fsync is currently already in progress in a
|
||||
* BIO thread. */
|
||||
int aofFsyncInProgress(void) {
|
||||
return bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
|
||||
}
|
||||
|
||||
/* Starts a background task that performs fsync() against the specified
|
||||
* file descriptor (the one of the AOF file) in another thread. */
|
||||
void aof_background_fsync(int fd) {
|
||||
@@ -333,10 +339,24 @@ void flushAppendOnlyFile(int force) {
|
||||
int sync_in_progress = 0;
|
||||
mstime_t latency;
|
||||
|
||||
if (sdslen(server.aof_buf) == 0) return;
|
||||
if (sdslen(server.aof_buf) == 0) {
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
|
||||
* called only when aof buffer is not empty, so if users
|
||||
* stop write commands before fsync called in one second,
|
||||
* the data in page cache cannot be flushed in time. */
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.aof_fsync_offset != server.aof_current_size &&
|
||||
server.unixtime > server.aof_last_fsync &&
|
||||
!(sync_in_progress = aofFsyncInProgress())) {
|
||||
goto try_fsync;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
|
||||
sync_in_progress = aofFsyncInProgress();
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
|
||||
/* With this append fsync policy we do background fsyncing.
|
||||
@@ -468,6 +488,7 @@ void flushAppendOnlyFile(int force) {
|
||||
server.aof_buf = sdsempty();
|
||||
}
|
||||
|
||||
try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (server.aof_no_fsync_on_rewrite &&
|
||||
@@ -482,10 +503,14 @@ void flushAppendOnlyFile(int force) {
|
||||
redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.unixtime > server.aof_last_fsync)) {
|
||||
if (!sync_in_progress) aof_background_fsync(server.aof_fd);
|
||||
if (!sync_in_progress) {
|
||||
aof_background_fsync(server.aof_fd);
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
}
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
}
|
||||
}
|
||||
@@ -690,6 +715,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
* operation is received. */
|
||||
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
server.aof_current_size = 0;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
fclose(fp);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -828,6 +854,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
stopLoading();
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
return C_OK;
|
||||
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
@@ -1235,7 +1262,7 @@ int rewriteModuleObject(rio *r, robj *key, robj *o) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,r);
|
||||
moduleInitIOContext(io,mt,r,key);
|
||||
mt->aof_rewrite(&io,key,mv->value);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@@ -1734,6 +1761,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_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. */
|
||||
|
||||
+8
-2
@@ -994,12 +994,18 @@ void bitfieldCommand(client *c) {
|
||||
/* Lookup for read is ok if key doesn't exit, but errors
|
||||
* if it's not a string. */
|
||||
o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
|
||||
if (o != NULL && checkType(c,o,OBJ_STRING)) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/* Lookup by making room up to the farest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
highest_write_offset)) == NULL) return;
|
||||
highest_write_offset)) == NULL) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,numops);
|
||||
|
||||
@@ -262,6 +262,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) {
|
||||
@@ -457,6 +467,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
server.fixed_time_expire--;
|
||||
|
||||
/* Free this item. */
|
||||
decrRefCount(rl->key);
|
||||
|
||||
+7
-5
@@ -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 =
|
||||
@@ -3031,6 +3032,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
if (server.cluster->mf_end) {
|
||||
server.cluster->failover_auth_time = mstime();
|
||||
server.cluster->failover_auth_rank = 0;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
|
||||
}
|
||||
serverLog(LL_WARNING,
|
||||
"Start of election delayed for %lld milliseconds "
|
||||
@@ -4775,7 +4777,7 @@ NULL
|
||||
|
||||
/* Generates a DUMP-format representation of the object 'o', adding it to the
|
||||
* io stream pointed by 'rio'. This function can't fail. */
|
||||
void createDumpPayload(rio *payload, robj *o) {
|
||||
void createDumpPayload(rio *payload, robj *o, robj *key) {
|
||||
unsigned char buf[2];
|
||||
uint64_t crc;
|
||||
|
||||
@@ -4783,7 +4785,7 @@ void createDumpPayload(rio *payload, robj *o) {
|
||||
* byte followed by the serialized object. This is understood by RESTORE. */
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o,key));
|
||||
|
||||
/* Write the footer, this is how it looks like:
|
||||
* ----------------+---------------------+---------------+
|
||||
@@ -4841,7 +4843,7 @@ void dumpCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Create the DUMP encoded representation. */
|
||||
createDumpPayload(&payload,o);
|
||||
createDumpPayload(&payload,o,c->argv[1]);
|
||||
|
||||
/* Transfer to the client */
|
||||
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
|
||||
@@ -4914,7 +4916,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload)) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@@ -5202,7 +5204,7 @@ try_again:
|
||||
|
||||
/* Emit the payload argument, that is the serialized object using
|
||||
* the DUMP format. */
|
||||
createDumpPayload(&payload,ov[j]);
|
||||
createDumpPayload(&payload,ov[j],kv[j]);
|
||||
serverAssertWithInfo(c,NULL,
|
||||
rioWriteBulkString(&cmd,payload.io.buffer.ptr,
|
||||
sdslen(payload.io.buffer.ptr)));
|
||||
|
||||
+6
-7
@@ -1042,8 +1042,8 @@ void configSetCommand(client *c) {
|
||||
int soft_seconds;
|
||||
|
||||
class = getClientTypeByName(v[j]);
|
||||
hard = strtoll(v[j+1],NULL,10);
|
||||
soft = strtoll(v[j+2],NULL,10);
|
||||
hard = memtoll(v[j+1],NULL);
|
||||
soft = memtoll(v[j+2],NULL);
|
||||
soft_seconds = strtoll(v[j+3],NULL,10);
|
||||
|
||||
server.client_obuf_limits[class].hard_limit_bytes = hard;
|
||||
@@ -1248,7 +1248,7 @@ void configSetCommand(client *c) {
|
||||
if (server.maxmemory < zmalloc_used_memory()) {
|
||||
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.");
|
||||
}
|
||||
freeMemoryIfNeeded();
|
||||
freeMemoryIfNeededAndSafe();
|
||||
}
|
||||
} config_set_memory_field(
|
||||
"proto-max-bulk-len",server.proto_max_bulk_len) {
|
||||
@@ -1654,12 +1654,11 @@ void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *
|
||||
* If the old file does not exist at all, an empty state is returned. */
|
||||
struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
FILE *fp = fopen(path,"r");
|
||||
struct rewriteConfigState *state = zmalloc(sizeof(*state));
|
||||
char buf[CONFIG_MAX_LINE+1];
|
||||
int linenum = -1;
|
||||
|
||||
if (fp == NULL && errno != ENOENT) return NULL;
|
||||
|
||||
char buf[CONFIG_MAX_LINE+1];
|
||||
int linenum = -1;
|
||||
struct rewriteConfigState *state = zmalloc(sizeof(*state));
|
||||
state->option_to_line = dictCreate(&optionToLineDictType,NULL);
|
||||
state->rewritten = dictCreate(&optionSetDictType,NULL);
|
||||
state->numlines = 0;
|
||||
|
||||
+3
-1
@@ -62,7 +62,9 @@
|
||||
#endif
|
||||
|
||||
/* Test for backtrace() */
|
||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__))
|
||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
|
||||
defined(__FreeBSD__) || (defined(__OpenBSD__) && defined(USE_BACKTRACE))\
|
||||
|| defined(__DragonFly__)
|
||||
#define HAVE_BACKTRACE 1
|
||||
#endif
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
* 2) clients WATCHing for the destination key notified.
|
||||
* 3) The expire time of the key is reset (the key is made persistent).
|
||||
*
|
||||
* All the new keys in the database should be craeted via this interface. */
|
||||
* All the new keys in the database should be created via this interface. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+323
-135
@@ -37,7 +37,11 @@
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#include <execinfo.h>
|
||||
#ifndef __OpenBSD__
|
||||
#include <ucontext.h>
|
||||
#else
|
||||
typedef ucontext_t sigcontext_t;
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include "bio.h"
|
||||
#include <unistd.h>
|
||||
@@ -70,7 +74,7 @@ void xorDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
digest[j] ^= hash[j];
|
||||
}
|
||||
|
||||
void xorObjectDigest(unsigned char *digest, robj *o) {
|
||||
void xorStringObjectDigest(unsigned char *digest, robj *o) {
|
||||
o = getDecodedObject(o);
|
||||
xorDigest(digest,o->ptr,sdslen(o->ptr));
|
||||
decrRefCount(o);
|
||||
@@ -100,12 +104,151 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
SHA1Final(digest,&ctx);
|
||||
}
|
||||
|
||||
void mixObjectDigest(unsigned char *digest, robj *o) {
|
||||
void mixStringObjectDigest(unsigned char *digest, robj *o) {
|
||||
o = getDecodedObject(o);
|
||||
mixDigest(digest,o->ptr,sdslen(o->ptr));
|
||||
decrRefCount(o);
|
||||
}
|
||||
|
||||
/* This function computes the digest of a data structure stored in the
|
||||
* object 'o'. It is the core of the DEBUG DIGEST command: when taking the
|
||||
* digest of a whole dataset, we take the digest of the key and the value
|
||||
* pair, and xor all those together.
|
||||
*
|
||||
* Note that this function does not reset the initial 'digest' passed, it
|
||||
* will continue mixing this object digest to anything that was already
|
||||
* present. */
|
||||
void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) {
|
||||
uint32_t aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
long long expiretime = getExpire(db,keyobj);
|
||||
char buf[128];
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
mixStringObjectDigest(digest,o);
|
||||
} else if (o->type == OBJ_LIST) {
|
||||
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
|
||||
listTypeEntry entry;
|
||||
while(listTypeNext(li,&entry)) {
|
||||
robj *eleobj = listTypeGet(&entry);
|
||||
mixStringObjectDigest(digest,eleobj);
|
||||
decrRefCount(eleobj);
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
} else if (o->type == OBJ_SET) {
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
sds sdsele;
|
||||
while((sdsele = setTypeNextObject(si)) != NULL) {
|
||||
xorDigest(digest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
unsigned char eledigest[20];
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = o->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vll;
|
||||
double score;
|
||||
|
||||
eptr = ziplistIndex(zl,0);
|
||||
serverAssert(eptr != NULL);
|
||||
sptr = ziplistNext(zl,eptr);
|
||||
serverAssert(sptr != NULL);
|
||||
|
||||
while (eptr != NULL) {
|
||||
serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
|
||||
score = zzlGetScore(sptr);
|
||||
|
||||
memset(eledigest,0,20);
|
||||
if (vstr != NULL) {
|
||||
mixDigest(eledigest,vstr,vlen);
|
||||
} else {
|
||||
ll2string(buf,sizeof(buf),vll);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
}
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",score);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
zzlNext(zl,&eptr,&sptr);
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",*score);
|
||||
memset(eledigest,0,20);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
hashTypeIterator *hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
unsigned char eledigest[20];
|
||||
sds sdsele;
|
||||
|
||||
memset(eledigest,0,20);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,o->ptr,NULL,NULL,0);
|
||||
streamID id;
|
||||
int64_t numfields;
|
||||
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
|
||||
mixDigest(digest,itemid,sdslen(itemid));
|
||||
sdsfree(itemid);
|
||||
|
||||
while(numfields--) {
|
||||
unsigned char *field, *value;
|
||||
int64_t field_len, value_len;
|
||||
streamIteratorGetField(&si,&field,&value,
|
||||
&field_len,&value_len);
|
||||
mixDigest(digest,field,field_len);
|
||||
mixDigest(digest,value,value_len);
|
||||
}
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
RedisModuleDigest md;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitDigestContext(md);
|
||||
if (mt->digest) {
|
||||
mt->digest(&md,mv->value);
|
||||
xorDigest(digest,md.x,sizeof(md.x));
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
/* If the key has an expire, add it to the mix */
|
||||
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
|
||||
}
|
||||
|
||||
/* Compute the dataset digest. Since keys, sets elements, hashes elements
|
||||
* are not ordered, we use a trick: every aggregate digest is the xor
|
||||
* of the digests of their elements. This way the order will not change
|
||||
@@ -114,7 +257,6 @@ void mixObjectDigest(unsigned char *digest, robj *o) {
|
||||
* a different digest. */
|
||||
void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char digest[20];
|
||||
char buf[128];
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
@@ -137,7 +279,6 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key;
|
||||
robj *keyobj, *o;
|
||||
long long expiretime;
|
||||
|
||||
memset(digest,0,20); /* This key-val digest */
|
||||
key = dictGetKey(de);
|
||||
@@ -146,134 +287,8 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
mixDigest(digest,key,sdslen(key));
|
||||
|
||||
o = dictGetVal(de);
|
||||
xorObjectDigest(db,keyobj,digest,o);
|
||||
|
||||
aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
expiretime = getExpire(db,keyobj);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
mixObjectDigest(digest,o);
|
||||
} else if (o->type == OBJ_LIST) {
|
||||
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
|
||||
listTypeEntry entry;
|
||||
while(listTypeNext(li,&entry)) {
|
||||
robj *eleobj = listTypeGet(&entry);
|
||||
mixObjectDigest(digest,eleobj);
|
||||
decrRefCount(eleobj);
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
} else if (o->type == OBJ_SET) {
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
sds sdsele;
|
||||
while((sdsele = setTypeNextObject(si)) != NULL) {
|
||||
xorDigest(digest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
unsigned char eledigest[20];
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = o->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vll;
|
||||
double score;
|
||||
|
||||
eptr = ziplistIndex(zl,0);
|
||||
serverAssert(eptr != NULL);
|
||||
sptr = ziplistNext(zl,eptr);
|
||||
serverAssert(sptr != NULL);
|
||||
|
||||
while (eptr != NULL) {
|
||||
serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
|
||||
score = zzlGetScore(sptr);
|
||||
|
||||
memset(eledigest,0,20);
|
||||
if (vstr != NULL) {
|
||||
mixDigest(eledigest,vstr,vlen);
|
||||
} else {
|
||||
ll2string(buf,sizeof(buf),vll);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
}
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",score);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
zzlNext(zl,&eptr,&sptr);
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",*score);
|
||||
memset(eledigest,0,20);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
hashTypeIterator *hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
unsigned char eledigest[20];
|
||||
sds sdsele;
|
||||
|
||||
memset(eledigest,0,20);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,o->ptr,NULL,NULL,0);
|
||||
streamID id;
|
||||
int64_t numfields;
|
||||
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
|
||||
mixDigest(digest,itemid,sdslen(itemid));
|
||||
sdsfree(itemid);
|
||||
|
||||
while(numfields--) {
|
||||
unsigned char *field, *value;
|
||||
int64_t field_len, value_len;
|
||||
streamIteratorGetField(&si,&field,&value,
|
||||
&field_len,&value_len);
|
||||
mixDigest(digest,field,field_len);
|
||||
mixDigest(digest,value,value_len);
|
||||
}
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
RedisModuleDigest md;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitDigestContext(md);
|
||||
if (mt->digest) {
|
||||
mt->digest(&md,mv->value);
|
||||
xorDigest(digest,md.x,sizeof(md.x));
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
/* If the key has an expire, add it to the mix */
|
||||
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
|
||||
/* We can finally xor the key-val digest to the final digest */
|
||||
xorDigest(final,digest,20);
|
||||
decrRefCount(keyobj);
|
||||
@@ -289,6 +304,7 @@ void debugCommand(client *c) {
|
||||
"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
|
||||
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
||||
"DIGEST -- Output a hex signature representing the current DB content.",
|
||||
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
|
||||
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
||||
"LOG <message> -- write message to the server log.",
|
||||
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
|
||||
@@ -306,6 +322,7 @@ void debugCommand(client *c) {
|
||||
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@@ -332,7 +349,6 @@ NULL
|
||||
zfree(ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"assert")) {
|
||||
if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
serverAssertWithInfo(c,c->argv[0],1 == 2);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
|
||||
@@ -491,15 +507,28 @@ NULL
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
|
||||
/* DEBUG DIGEST (form without keys specified) */
|
||||
unsigned char digest[20];
|
||||
sds d = sdsempty();
|
||||
int j;
|
||||
|
||||
computeDatasetDigest(digest);
|
||||
for (j = 0; j < 20; j++)
|
||||
d = sdscatprintf(d, "%02x",digest[j]);
|
||||
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
|
||||
addReplyStatus(c,d);
|
||||
sdsfree(d);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"digest-value") && c->argc >= 2) {
|
||||
/* DEBUG DIGEST-VALUE key key key ... key. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
unsigned char digest[20];
|
||||
memset(digest,0,20); /* Start with a clean result */
|
||||
robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
|
||||
if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
|
||||
|
||||
sds d = sdsempty();
|
||||
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
|
||||
addReplyStatus(c,d);
|
||||
sdsfree(d);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
long long utime = dtime*1000000;
|
||||
@@ -591,6 +620,10 @@ NULL
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stringmatch-test") && c->argc == 2)
|
||||
{
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -729,6 +762,22 @@ static void *getMcontextEip(ucontext_t *uc) {
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
return (void*) uc->uc_mcontext.pc;
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
/* FreeBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.mc_eip;
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
#endif
|
||||
#elif defined(__OpenBSD__)
|
||||
/* OpenBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->sc_eip;
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->sc_rip;
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
@@ -870,6 +919,145 @@ void logRegisters(ucontext_t *uc) {
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
#if defined(__x86_64__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
|
||||
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->uc_mcontext.mc_rax,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rcx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbp,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsp,
|
||||
(unsigned long) uc->uc_mcontext.mc_r8,
|
||||
(unsigned long) uc->uc_mcontext.mc_r9,
|
||||
(unsigned long) uc->uc_mcontext.mc_r10,
|
||||
(unsigned long) uc->uc_mcontext.mc_r11,
|
||||
(unsigned long) uc->uc_mcontext.mc_r12,
|
||||
(unsigned long) uc->uc_mcontext.mc_r13,
|
||||
(unsigned long) uc->uc_mcontext.mc_r14,
|
||||
(unsigned long) uc->uc_mcontext.mc_r15,
|
||||
(unsigned long) uc->uc_mcontext.mc_rip,
|
||||
(unsigned long) uc->uc_mcontext.mc_rflags,
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#elif defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
|
||||
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
|
||||
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
|
||||
(unsigned long) uc->uc_mcontext.mc_eax,
|
||||
(unsigned long) uc->uc_mcontext.mc_ebx,
|
||||
(unsigned long) uc->uc_mcontext.mc_ebx,
|
||||
(unsigned long) uc->uc_mcontext.mc_edx,
|
||||
(unsigned long) uc->uc_mcontext.mc_edi,
|
||||
(unsigned long) uc->uc_mcontext.mc_esi,
|
||||
(unsigned long) uc->uc_mcontext.mc_ebp,
|
||||
(unsigned long) uc->uc_mcontext.mc_esp,
|
||||
(unsigned long) uc->uc_mcontext.mc_ss,
|
||||
(unsigned long) uc->uc_mcontext.mc_eflags,
|
||||
(unsigned long) uc->uc_mcontext.mc_eip,
|
||||
(unsigned long) uc->uc_mcontext.mc_cs,
|
||||
(unsigned long) uc->uc_mcontext.mc_es,
|
||||
(unsigned long) uc->uc_mcontext.mc_fs,
|
||||
(unsigned long) uc->uc_mcontext.mc_gs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_esp);
|
||||
#endif
|
||||
#elif defined(__OpenBSD__)
|
||||
#if defined(__x86_64__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
|
||||
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->sc_rax,
|
||||
(unsigned long) uc->sc_rbx,
|
||||
(unsigned long) uc->sc_rcx,
|
||||
(unsigned long) uc->sc_rdx,
|
||||
(unsigned long) uc->sc_rdi,
|
||||
(unsigned long) uc->sc_rsi,
|
||||
(unsigned long) uc->sc_rbp,
|
||||
(unsigned long) uc->sc_rsp,
|
||||
(unsigned long) uc->sc_r8,
|
||||
(unsigned long) uc->sc_r9,
|
||||
(unsigned long) uc->sc_r10,
|
||||
(unsigned long) uc->sc_r11,
|
||||
(unsigned long) uc->sc_r12,
|
||||
(unsigned long) uc->sc_r13,
|
||||
(unsigned long) uc->sc_r14,
|
||||
(unsigned long) uc->sc_r15,
|
||||
(unsigned long) uc->sc_rip,
|
||||
(unsigned long) uc->sc_rflags,
|
||||
(unsigned long) uc->sc_cs
|
||||
);
|
||||
logStackContent((void**)uc->sc_rsp);
|
||||
#elif defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
|
||||
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
|
||||
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
|
||||
(unsigned long) uc->sc_eax,
|
||||
(unsigned long) uc->sc_ebx,
|
||||
(unsigned long) uc->sc_ebx,
|
||||
(unsigned long) uc->sc_edx,
|
||||
(unsigned long) uc->sc_edi,
|
||||
(unsigned long) uc->sc_esi,
|
||||
(unsigned long) uc->sc_ebp,
|
||||
(unsigned long) uc->sc_esp,
|
||||
(unsigned long) uc->sc_ss,
|
||||
(unsigned long) uc->sc_eflags,
|
||||
(unsigned long) uc->sc_eip,
|
||||
(unsigned long) uc->sc_cs,
|
||||
(unsigned long) uc->sc_es,
|
||||
(unsigned long) uc->sc_fs,
|
||||
(unsigned long) uc->sc_gs
|
||||
);
|
||||
logStackContent((void**)uc->sc_esp);
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
|
||||
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->uc_mcontext.mc_rax,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rcx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbp,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsp,
|
||||
(unsigned long) uc->uc_mcontext.mc_r8,
|
||||
(unsigned long) uc->uc_mcontext.mc_r9,
|
||||
(unsigned long) uc->uc_mcontext.mc_r10,
|
||||
(unsigned long) uc->uc_mcontext.mc_r11,
|
||||
(unsigned long) uc->uc_mcontext.mc_r12,
|
||||
(unsigned long) uc->uc_mcontext.mc_r13,
|
||||
(unsigned long) uc->uc_mcontext.mc_r14,
|
||||
(unsigned long) uc->uc_mcontext.mc_r15,
|
||||
(unsigned long) uc->uc_mcontext.mc_rip,
|
||||
(unsigned long) uc->uc_mcontext.mc_rflags,
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#else
|
||||
serverLog(LL_WARNING,
|
||||
" Dumping of registers not supported for this OS/arch");
|
||||
|
||||
+3
-3
@@ -47,7 +47,7 @@ int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
|
||||
/* forward declarations*/
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, long *defragged);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
@@ -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 */
|
||||
unsigned int hash = dictGetHash(d, sdsele);
|
||||
uint64_t hash = dictGetHash(d, sdsele);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
ln->value = newsds;
|
||||
defragged++;
|
||||
@@ -392,7 +392,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
* moved. Return value is the the dictEntry if found, or NULL if not found.
|
||||
* NOTE: this is very ugly code, but it let's us avoid the complication of
|
||||
* doing a scan on another dict. */
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, long *defragged) {
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
|
||||
if (deref) {
|
||||
dictEntry *de = *deref;
|
||||
|
||||
+13
-2
@@ -444,8 +444,8 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
* Otehrwise if we are over the memory limit, but not enough memory
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
/* By default slaves should ignore maxmemory and just be masters excat
|
||||
* copies. */
|
||||
/* By default replicas should ignore maxmemory
|
||||
* and just be masters exact copies. */
|
||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
|
||||
size_t mem_reported, mem_tofree, mem_freed;
|
||||
@@ -622,3 +622,14 @@ cant_free:
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
|
||||
* function if right now there are the conditions to do so safely:
|
||||
*
|
||||
* - There must be no script in timeout condition.
|
||||
* - Nor we are loading data right now.
|
||||
*
|
||||
*/
|
||||
int freeMemoryIfNeededAndSafe(void) {
|
||||
if (server.lua_timedout || server.loading) return C_OK;
|
||||
return freeMemoryIfNeeded();
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
|
||||
+9
-2
@@ -614,6 +614,7 @@ int hllSparseToDense(robj *o) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
|
||||
idx++;
|
||||
@@ -699,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;
|
||||
@@ -1013,7 +1014,12 @@ uint64_t hllCount(struct hllhdr *hdr, int *invalid) {
|
||||
double m = HLL_REGISTERS;
|
||||
double E;
|
||||
int j;
|
||||
int reghisto[HLL_Q+2] = {0};
|
||||
/* Note that reghisto size could be just HLL_Q+2, becuase HLL_Q+1 is
|
||||
* the maximum frequency of the "000...1" sequence the hash function is
|
||||
* able to return. However it is slow to check for sanity of the
|
||||
* input: instead we history array at a safe size: overflows will
|
||||
* just write data to wrong, but correctly allocated, places. */
|
||||
int reghisto[64] = {0};
|
||||
|
||||
/* Compute register histogram */
|
||||
if (hdr->encoding == HLL_DENSE) {
|
||||
@@ -1088,6 +1094,7 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
if (regval > max[i]) max[i] = regval;
|
||||
i++;
|
||||
|
||||
+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) && \
|
||||
|
||||
+648
-56
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,6 @@ hellotimer.so: hellotimer.xo
|
||||
hellodict.xo: ../redismodule.h
|
||||
|
||||
hellodict.so: hellodict.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
|
||||
+17
@@ -35,6 +35,7 @@
|
||||
void initClientMultiState(client *c) {
|
||||
c->mstate.commands = NULL;
|
||||
c->mstate.count = 0;
|
||||
c->mstate.cmd_flags = 0;
|
||||
}
|
||||
|
||||
/* Release all the resources associated with MULTI/EXEC state */
|
||||
@@ -67,6 +68,7 @@ void queueMultiCommand(client *c) {
|
||||
for (j = 0; j < c->argc; j++)
|
||||
incrRefCount(mc->argv[j]);
|
||||
c->mstate.count++;
|
||||
c->mstate.cmd_flags |= c->cmd->flags;
|
||||
}
|
||||
|
||||
void discardTransaction(client *c) {
|
||||
@@ -137,6 +139,21 @@ void execCommand(client *c) {
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
/* If there are write commands inside the transaction, and this is a read
|
||||
* only slave, we want to send an error. This happens when the transaction
|
||||
* was initiated when the instance was a master or a writable replica and
|
||||
* then the configuration changed (for example instance was turned into
|
||||
* a replica). */
|
||||
if (!server.loading && server.masterhost && server.repl_slave_ro &&
|
||||
!(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
|
||||
{
|
||||
addReplyError(c,
|
||||
"Transaction contains write commands but instance "
|
||||
"is now a read-only slave. EXEC aborted.");
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
/* Exec all the queued commands */
|
||||
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
|
||||
orig_argv = c->argv;
|
||||
|
||||
+47
-26
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "server.h"
|
||||
#include "atomicvar.h"
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
@@ -365,19 +366,13 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
* Where the master must propagate the first change even if the second
|
||||
* will produce an error. However it is useful to log such events since
|
||||
* they are rare and may hint at errors in a script or a bug in Redis. */
|
||||
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE)) {
|
||||
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
|
||||
char* to = c->flags & CLIENT_MASTER? "master": "replica";
|
||||
char* from = c->flags & CLIENT_MASTER? "replica": "master";
|
||||
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
/* Here we want to panic because when a master is sending an
|
||||
* error to some slave in the context of replication, this can
|
||||
* only create some kind of offset or data desynchronization. Better
|
||||
* to catch it ASAP and crash instead of continuing. */
|
||||
if (c->flags & CLIENT_SLAVE)
|
||||
serverPanic("Continuing is unsafe: replication protocol violation.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,6 +629,19 @@ void addReplySubcommandSyntaxError(client *c) {
|
||||
sdsfree(cmd);
|
||||
}
|
||||
|
||||
/* Append 'src' client output buffers into 'dst' client output buffers.
|
||||
* This function clears the output buffers of 'src' */
|
||||
void AddReplyFromClient(client *dst, client *src) {
|
||||
if (prepareClientToWrite(dst) != C_OK)
|
||||
return;
|
||||
addReplyString(dst,src->buf, src->bufpos);
|
||||
if (listLength(src->reply))
|
||||
listJoin(dst->reply,src->reply);
|
||||
dst->reply_bytes += src->reply_bytes;
|
||||
src->reply_bytes = 0;
|
||||
src->bufpos = 0;
|
||||
}
|
||||
|
||||
/* Copy 'src' client output buffers into 'dst' client output buffers.
|
||||
* The function takes care of freeing the old output buffers of the
|
||||
* destination client. */
|
||||
@@ -801,6 +809,16 @@ void unlinkClient(client *c) {
|
||||
c->client_list_node = NULL;
|
||||
}
|
||||
|
||||
/* In the case of diskless replication the fork is writing to the
|
||||
* sockets and just closing the fd isn't enough, if we don't also
|
||||
* shutdown the socket the fork will continue to write to the slave
|
||||
* and the salve will only find out that it was disconnected when
|
||||
* it will finish reading the rdb. */
|
||||
if ((c->flags & CLIENT_SLAVE) &&
|
||||
(c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)) {
|
||||
shutdown(c->fd, SHUT_RDWR);
|
||||
}
|
||||
|
||||
/* Unregister async I/O handlers and close the socket. */
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
@@ -1470,7 +1488,7 @@ void processInputBuffer(client *c) {
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
if (c->qb_pos) {
|
||||
if (server.current_client != NULL && c->qb_pos) {
|
||||
sdsrange(c->querybuf,c->qb_pos,-1);
|
||||
c->qb_pos = 0;
|
||||
}
|
||||
@@ -2003,15 +2021,8 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the number of bytes that Redis is virtually
|
||||
/* This function returns the number of bytes that Redis is
|
||||
* using to store the reply still not read by the client.
|
||||
* It is "virtual" since the reply output list may contain objects that
|
||||
* are shared and are not really using additional memory.
|
||||
*
|
||||
* The function returns the total sum of the length of all the objects
|
||||
* stored in the output list, plus the memory used to allocate every
|
||||
* list node. The static reply buffer is not taken into account since it
|
||||
* is allocated anyway.
|
||||
*
|
||||
* Note: this function is very fast so can be called as many time as
|
||||
* the caller wishes. The main usage of this function currently is
|
||||
@@ -2132,17 +2143,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);
|
||||
|
||||
+43
-23
@@ -415,6 +415,18 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Optimize the SDS string inside the string object to require little space,
|
||||
* in case there is more than 10% of free space at the end of the SDS
|
||||
* string. This happens because SDS strings tend to overallocate to avoid
|
||||
* wasting too much time in allocations when appending to the string. */
|
||||
void trimStringObjectIfNeeded(robj *o) {
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(o->ptr) > sdslen(o->ptr)/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space */
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
@@ -455,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,11 +501,7 @@ robj *tryObjectEncoding(robj *o) {
|
||||
* We do that only for relatively large strings as this branch
|
||||
* is only entered if the length of the string is greater than
|
||||
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
trimStringObjectIfNeeded(o);
|
||||
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
@@ -826,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);
|
||||
@@ -1191,7 +1206,7 @@ sds getMemoryDoctorReport(void) {
|
||||
|
||||
/* Set the object LRU/LFU depending on server.maxmemory_policy.
|
||||
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* is MAXMEMORY_FLAG_LRU.
|
||||
* Either or both of them may be <0, in that case, nothing is set. */
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
@@ -1202,16 +1217,20 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
}
|
||||
} else if (lru_idle >= 0) {
|
||||
/* Serialized LRU idle time is in seconds. Scale
|
||||
/* Provided LRU idle time is in seconds. Scale
|
||||
* according to the LRU clock resolution this Redis
|
||||
* instance was compiled with (normally 1000 ms, so the
|
||||
* below statement will expand to lru_idle*1000/1000. */
|
||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
||||
val->lru = lru_clock - lru_idle;
|
||||
/* If the lru field overflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide the maximum
|
||||
* representable idle time. */
|
||||
if (val->lru < 0) val->lru = lru_clock+1;
|
||||
long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
|
||||
/* If the LRU field underflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide a large enough LRU
|
||||
* that is half-way in the circlular LRU clock we use: this way
|
||||
* the computed idle time for this object will stay high for quite
|
||||
* some time. */
|
||||
if (lru_abs < 0)
|
||||
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
||||
val->lru = lru_abs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,8 +1304,6 @@ NULL
|
||||
*
|
||||
* Usage: MEMORY usage <key> */
|
||||
void memoryCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||
const char *help[] = {
|
||||
"DOCTOR - Return memory problems reports.",
|
||||
@@ -1298,6 +1315,7 @@ NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"usage") && c->argc >= 3) {
|
||||
dictEntry *de;
|
||||
long long samples = OBJ_COMPUTE_SIZE_DEF_SAMPLES;
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
if (!strcasecmp(c->argv[j]->ptr,"samples") &&
|
||||
@@ -1316,10 +1334,12 @@ NULL
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
size_t usage = objectComputeSize(o,samples);
|
||||
usage += sdsAllocSize(c->argv[2]->ptr);
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
addReply(c, shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize(dictGetVal(de),samples);
|
||||
usage += sdsAllocSize(dictGetKey(de));
|
||||
usage += sizeof(dictEntry);
|
||||
addReplyLongLong(c,usage);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
|
||||
@@ -751,7 +751,7 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
|
||||
|
||||
/* Save a Redis object.
|
||||
* Returns -1 on error, number of bytes written on success. */
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
ssize_t n = 0, nwritten = 0;
|
||||
|
||||
if (o->type == OBJ_STRING) {
|
||||
@@ -966,7 +966,6 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
|
||||
/* 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) {
|
||||
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);
|
||||
@@ -996,7 +998,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o);
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@@ -1038,7 +1040,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveObjectType(rdb,val) == -1) return -1;
|
||||
if (rdbSaveStringObject(rdb,key) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val,key) == -1) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1091,6 +1093,45 @@ 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);
|
||||
|
||||
/* 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 +1153,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 +1215,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;
|
||||
|
||||
@@ -1380,7 +1424,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@@ -1645,6 +1689,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
* node: the entries inside the listpack itself are delta-encoded
|
||||
* relatively to this ID. */
|
||||
sds nodekey = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
|
||||
if (nodekey == NULL) {
|
||||
rdbExitReportCorruptRDB("Stream master ID loading failed: invalid encoding or I/O error.");
|
||||
}
|
||||
if (sdslen(nodekey) != sizeof(streamID)) {
|
||||
rdbExitReportCorruptRDB("Stream node key entry is not the "
|
||||
"size of a stream ID");
|
||||
@@ -1764,7 +1811,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@@ -1834,7 +1881,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);
|
||||
@@ -1971,15 +2018,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);
|
||||
@@ -1989,21 +2035,44 @@ 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. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Read key */
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
|
||||
@@ -140,11 +140,12 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb);
|
||||
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 *rdbLoadStringObject(rio *rdb);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
|
||||
#define ERROR(...) { \
|
||||
char __buf[1024]; \
|
||||
sprintf(__buf, __VA_ARGS__); \
|
||||
sprintf(error, "0x%16llx: %s", (long long)epos, __buf); \
|
||||
snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
|
||||
snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
|
||||
}
|
||||
|
||||
static char error[1024];
|
||||
static char error[1044];
|
||||
static off_t epos;
|
||||
|
||||
int consumeNewline(char *buf) {
|
||||
|
||||
@@ -285,7 +285,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
||||
+532
-217
File diff suppressed because it is too large
Load Diff
+60
-1
@@ -85,6 +85,26 @@
|
||||
#define REDISMODULE_CTX_FLAGS_OOM (1<<10)
|
||||
/* Less than 25% of memory available according to maxmemory. */
|
||||
#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) /* $ */
|
||||
@@ -123,12 +143,21 @@
|
||||
|
||||
#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
|
||||
* of timers that are going to expire, sorted by expire time. */
|
||||
typedef uint64_t RedisModuleTimerID;
|
||||
|
||||
/* CommandFilter Flags */
|
||||
|
||||
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
||||
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
@@ -147,20 +176,25 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
typedef struct RedisModuleClusterInfo RedisModuleClusterInfo;
|
||||
typedef struct RedisModuleDict RedisModuleDict;
|
||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
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);
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
||||
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;
|
||||
@@ -169,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) \
|
||||
@@ -214,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);
|
||||
@@ -275,6 +313,7 @@ int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, Re
|
||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
|
||||
@@ -332,6 +371,15 @@ void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname);
|
||||
RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
@@ -354,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);
|
||||
@@ -437,6 +486,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(RetainString);
|
||||
REDISMODULE_GET_API(StringCompare);
|
||||
REDISMODULE_GET_API(GetContextFromIO);
|
||||
REDISMODULE_GET_API(GetKeyNameFromIO);
|
||||
REDISMODULE_GET_API(Milliseconds);
|
||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||
REDISMODULE_GET_API(DigestAddLongLong);
|
||||
@@ -492,6 +542,15 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(GetRandomBytes);
|
||||
REDISMODULE_GET_API(GetRandomHexChars);
|
||||
REDISMODULE_GET_API(SetClusterFlags);
|
||||
REDISMODULE_GET_API(ExportSharedAPI);
|
||||
REDISMODULE_GET_API(GetSharedAPI);
|
||||
REDISMODULE_GET_API(RegisterCommandFilter);
|
||||
REDISMODULE_GET_API(UnregisterCommandFilter);
|
||||
REDISMODULE_GET_API(CommandFilterArgsCount);
|
||||
REDISMODULE_GET_API(CommandFilterArgGet);
|
||||
REDISMODULE_GET_API(CommandFilterArgInsert);
|
||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
|
||||
+80
-22
@@ -30,6 +30,7 @@
|
||||
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
@@ -593,6 +594,7 @@ int startBgsaveForReplication(int mincapa) {
|
||||
client *slave = ln->value;
|
||||
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
||||
slave->replstate = REPL_STATE_NONE;
|
||||
slave->flags &= ~CLIENT_SLAVE;
|
||||
listDelNode(server.slaves,ln);
|
||||
addReplyError(slave,
|
||||
@@ -821,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! */
|
||||
@@ -840,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;
|
||||
@@ -963,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. */
|
||||
@@ -1089,14 +1115,23 @@ void replicationCreateMasterClient(int fd, int dbid) {
|
||||
if (dbid != -1) selectDb(server.master,dbid);
|
||||
}
|
||||
|
||||
void restartAOF() {
|
||||
int retry = 10;
|
||||
while (retry-- && startAppendOnly() == C_ERR) {
|
||||
serverLog(LL_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
|
||||
/* This function will try to re-enable the AOF file after the
|
||||
* master-replica synchronization: if it fails after multiple attempts
|
||||
* the replica cannot be considered reliable and exists with an
|
||||
* error. */
|
||||
void restartAOFAfterSYNC() {
|
||||
unsigned int tries, max_tries = 10;
|
||||
for (tries = 0; tries < max_tries; ++tries) {
|
||||
if (startAppendOnly() == C_OK) break;
|
||||
serverLog(LL_WARNING,
|
||||
"Failed enabling the AOF after successful master synchronization! "
|
||||
"Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (!retry) {
|
||||
serverLog(LL_WARNING,"FATAL: this replica instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
if (tries == max_tries) {
|
||||
serverLog(LL_WARNING,
|
||||
"FATAL: this replica instance finished the synchronization with "
|
||||
"its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1283,7 +1318,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
cancelReplicationHandshake();
|
||||
/* Re-enable the AOF if we disabled it earlier, in order to restore
|
||||
* the original configuration. */
|
||||
if (aof_is_enabled) restartAOF();
|
||||
if (aof_is_enabled) restartAOFAfterSYNC();
|
||||
return;
|
||||
}
|
||||
/* Final setup of the connected slave <- master link */
|
||||
@@ -1308,7 +1343,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
if (aof_is_enabled) restartAOF();
|
||||
if (aof_is_enabled) restartAOFAfterSYNC();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -1961,7 +1996,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;
|
||||
}
|
||||
|
||||
@@ -2031,6 +2069,15 @@ void replicaofCommand(client *c) {
|
||||
} else {
|
||||
long port;
|
||||
|
||||
if (c->flags & CLIENT_SLAVE)
|
||||
{
|
||||
/* If a client is already a replica they cannot run this command,
|
||||
* because it involves flushing all replicas (including this
|
||||
* client) */
|
||||
addReplyError(c, "Command is not valid when client is a replica.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
|
||||
return;
|
||||
|
||||
@@ -2574,10 +2621,21 @@ void replicationCron(void) {
|
||||
if ((replication_cron_loops % server.repl_ping_slave_period) == 0 &&
|
||||
listLength(server.slaves))
|
||||
{
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
/* Note that we don't send the PING if the clients are paused during
|
||||
* a Redis Cluster manual failover: the PING we send will otherwise
|
||||
* alter the replication offsets of master and slave, and will no longer
|
||||
* match the one stored into 'mf_master_offset' state. */
|
||||
int manual_failover_in_progress =
|
||||
server.cluster_enabled &&
|
||||
server.cluster->mf_end &&
|
||||
clientsArePaused();
|
||||
|
||||
if (!manual_failover_in_progress) {
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Second, send a newline to all the slaves in pre-synchronization
|
||||
|
||||
@@ -443,6 +443,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
|
||||
/* Process module hooks */
|
||||
moduleCallCommandFilters(c);
|
||||
argv = c->argv;
|
||||
argc = c->argc;
|
||||
|
||||
/* Log the command if debugging is active. */
|
||||
if (ldb.active && ldb.step) {
|
||||
sds cmdlog = sdsnew("<redis>");
|
||||
|
||||
@@ -257,8 +257,12 @@ sds sdsRemoveFreeSpace(sds s) {
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
|
||||
size_t len = sdslen(s);
|
||||
size_t avail = sdsavail(s);
|
||||
sh = (char*)s-oldhdrlen;
|
||||
|
||||
/* Return ASAP if there is no space left. */
|
||||
if (avail == 0) return s;
|
||||
|
||||
/* Check what would be the minimum SDS header that is just good enough to
|
||||
* fit this string. */
|
||||
type = sdsReqType(len);
|
||||
@@ -695,7 +699,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
* s = sdstrim(s,"Aa. :");
|
||||
* printf("%s\n", s);
|
||||
*
|
||||
* Output will be just "Hello World".
|
||||
* Output will be just "HelloWorld".
|
||||
*/
|
||||
sds sdstrim(sds s, const char *cset) {
|
||||
char *start, *end, *sp, *ep;
|
||||
|
||||
+74
-37
@@ -200,8 +200,8 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"bzpopmin",bzpopminCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmax",bzpopmaxCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmin",bzpopminCommand,-3,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"bzpopmax",bzpopmaxCommand,-3,"wsF",0,NULL,1,-2,1,0,0},
|
||||
{"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hget",hgetCommand,3,"rF",0,NULL,1,1,1,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. */
|
||||
@@ -1000,10 +1005,12 @@ void clientsCron(void) {
|
||||
void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (server.active_expire_enabled && server.masterhost == NULL) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else if (server.masterhost != NULL) {
|
||||
expireSlaveKeys();
|
||||
if (server.active_expire_enabled) {
|
||||
if (server.masterhost == NULL) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else {
|
||||
expireSlaveKeys();
|
||||
}
|
||||
}
|
||||
|
||||
/* Defrag keys gradually. */
|
||||
@@ -1052,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.
|
||||
@@ -1097,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
|
||||
@@ -1521,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();
|
||||
@@ -1958,9 +1976,13 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
}
|
||||
if (fds[*count] == ANET_ERR) {
|
||||
serverLog(LL_WARNING,
|
||||
"Creating Server TCP listening socket %s:%d: %s",
|
||||
"Could not create server TCP listening socket %s:%d: %s",
|
||||
server.bindaddr[j] ? server.bindaddr[j] : "*",
|
||||
port, server.neterr);
|
||||
if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
|
||||
errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
|
||||
errno == EAFNOSUPPORT || errno == EADDRNOTAVAIL)
|
||||
continue;
|
||||
return C_ERR;
|
||||
}
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
@@ -2021,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();
|
||||
@@ -2173,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();
|
||||
}
|
||||
@@ -2408,10 +2439,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) &&
|
||||
@@ -2429,7 +2463,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;
|
||||
@@ -2525,6 +2560,7 @@ void call(client *c, int flags) {
|
||||
redisOpArrayFree(&server.also_propagate);
|
||||
}
|
||||
server.also_propagate = prev_also_propagate;
|
||||
server.fixed_time_expire--;
|
||||
server.stat_numcommands++;
|
||||
}
|
||||
|
||||
@@ -2537,6 +2573,8 @@ void call(client *c, int flags) {
|
||||
* other operations can be performed by the caller. Otherwise
|
||||
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
|
||||
int processCommand(client *c) {
|
||||
moduleCallCommandFilters(c);
|
||||
|
||||
/* The QUIT command is handled separately. Normal command procs will
|
||||
* go through checking for replication and QUIT will cause trouble
|
||||
* when FORCE_REPLICATION is enabled and would be implemented in
|
||||
@@ -2603,17 +2641,13 @@ int processCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Handle the maxmemory directive.
|
||||
*
|
||||
* First we try to free some memory if possible (if there are volatile
|
||||
* keys in the dataset). If there are not the only thing we can do
|
||||
* is returning an error.
|
||||
*
|
||||
* Note that we do not want to reclaim memory if we are here re-entering
|
||||
* the event loop since there is a busy Lua script running in timeout
|
||||
* condition, to avoid mixing the propagation of scripts with the propagation
|
||||
* of DELs due to eviction. */
|
||||
* condition, to avoid mixing the propagation of scripts with the
|
||||
* propagation of DELs due to eviction. */
|
||||
if (server.maxmemory && !server.lua_timedout) {
|
||||
int out_of_memory = freeMemoryIfNeeded() == C_ERR;
|
||||
int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) return C_ERR;
|
||||
@@ -3243,11 +3277,11 @@ sds genRedisInfoString(char *section) {
|
||||
"allocator_frag_ratio:%.2f\r\n"
|
||||
"allocator_frag_bytes:%zu\r\n"
|
||||
"allocator_rss_ratio:%.2f\r\n"
|
||||
"allocator_rss_bytes:%zu\r\n"
|
||||
"allocator_rss_bytes:%zd\r\n"
|
||||
"rss_overhead_ratio:%.2f\r\n"
|
||||
"rss_overhead_bytes:%zu\r\n"
|
||||
"rss_overhead_bytes:%zd\r\n"
|
||||
"mem_fragmentation_ratio:%.2f\r\n"
|
||||
"mem_fragmentation_bytes:%zu\r\n"
|
||||
"mem_fragmentation_bytes:%zd\r\n"
|
||||
"mem_not_counted_for_evict:%zu\r\n"
|
||||
"mem_replication_backlog:%zu\r\n"
|
||||
"mem_clients_slaves:%zu\r\n"
|
||||
@@ -3803,6 +3837,7 @@ static void sigShutdownHandler(int sig) {
|
||||
rdbRemoveTempFile(getpid());
|
||||
exit(1); /* Exit with an error since this was not a clean shutdown. */
|
||||
} else if (server.loading) {
|
||||
serverLogFromHandler(LL_WARNING, "Received shutdown signal during loading, exiting now.");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -3859,12 +3894,14 @@ void loadDataFromDisk(void) {
|
||||
(float)(ustime()-start)/1000000);
|
||||
|
||||
/* Restore the replication ID / offset from the RDB file. */
|
||||
if (server.masterhost &&
|
||||
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));
|
||||
@@ -4018,8 +4055,6 @@ int main(int argc, char **argv) {
|
||||
return sha1Test(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "util")) {
|
||||
return utilTest(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "sds")) {
|
||||
return sdsTest(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "endianconv")) {
|
||||
return endianconvTest(argc, argv);
|
||||
} else if (!strcasecmp(argv[2], "crc64")) {
|
||||
@@ -4170,6 +4205,7 @@ int main(int argc, char **argv) {
|
||||
linuxMemoryWarnings();
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
InitServerLast();
|
||||
loadDataFromDisk();
|
||||
if (server.cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
@@ -4184,6 +4220,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();
|
||||
}
|
||||
|
||||
|
||||
+37
-10
@@ -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;
|
||||
|
||||
@@ -551,16 +561,18 @@ typedef struct RedisModuleIO {
|
||||
int ver; /* Module serialization version: 1 (old),
|
||||
* 2 (current version with opcodes annotation). */
|
||||
struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
|
||||
struct redisObject *key; /* Optional name of key processed */
|
||||
} RedisModuleIO;
|
||||
|
||||
/* Macro to initialize an IO context. Note that the 'ver' field is populated
|
||||
* inside rdb.c according to the version of the value to load. */
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr) do { \
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr,keyptr) do { \
|
||||
iovar.rio = rioptr; \
|
||||
iovar.type = mtype; \
|
||||
iovar.bytes = 0; \
|
||||
iovar.error = 0; \
|
||||
iovar.ver = 0; \
|
||||
iovar.key = keyptr; \
|
||||
iovar.ctx = NULL; \
|
||||
} while(0);
|
||||
|
||||
@@ -654,6 +666,9 @@ typedef struct multiCmd {
|
||||
typedef struct multiState {
|
||||
multiCmd *commands; /* Array of MULTI commands */
|
||||
int count; /* Total number of MULTI commands */
|
||||
int cmd_flags; /* The accumulated command flags OR-ed together.
|
||||
So if at least a command has a given flag, it
|
||||
will be set in this field. */
|
||||
int minreplicas; /* MINREPLICAS for synchronous replication */
|
||||
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
|
||||
} multiState;
|
||||
@@ -734,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. */
|
||||
@@ -864,11 +879,11 @@ struct redisMemOverhead {
|
||||
float dataset_perc;
|
||||
float peak_perc;
|
||||
float total_frag;
|
||||
size_t total_frag_bytes;
|
||||
ssize_t total_frag_bytes;
|
||||
float allocator_frag;
|
||||
size_t allocator_frag_bytes;
|
||||
ssize_t allocator_frag_bytes;
|
||||
float allocator_rss;
|
||||
size_t allocator_rss_bytes;
|
||||
ssize_t allocator_rss_bytes;
|
||||
float rss_extra;
|
||||
size_t rss_extra_bytes;
|
||||
size_t num_dbs;
|
||||
@@ -951,7 +966,9 @@ struct redisServer {
|
||||
size_t initial_memory_usage; /* Bytes used after initialization. */
|
||||
int always_show_logo; /* Show logo even for non-stdout logging. */
|
||||
/* Modules */
|
||||
dict *moduleapi; /* Exported APIs dictionary for modules. */
|
||||
dict *moduleapi; /* Exported core APIs dictionary for modules. */
|
||||
dict *sharedapi; /* Like moduleapi but containing the APIs that
|
||||
modules share with each other. */
|
||||
list *loadmodule_queue; /* List of modules to load at startup. */
|
||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||
client blocked on a module command needs
|
||||
@@ -972,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 */
|
||||
@@ -1058,6 +1076,7 @@ struct redisServer {
|
||||
off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */
|
||||
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
|
||||
off_t aof_current_size; /* AOF current size. */
|
||||
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
|
||||
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
||||
pid_t aof_child_pid; /* PID if rewriting process */
|
||||
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
|
||||
@@ -1214,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 */
|
||||
@@ -1402,7 +1422,8 @@ size_t moduleCount(void);
|
||||
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);
|
||||
@@ -1430,6 +1451,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReplyString(client *c, const char *s, size_t len);
|
||||
void AddReplyFromClient(client *c, client *src);
|
||||
void addReplyBulk(client *c, robj *obj);
|
||||
void addReplyBulkCString(client *c, const char *s);
|
||||
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
|
||||
@@ -1561,6 +1583,7 @@ int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
void trimStringObjectIfNeeded(robj *o);
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
@@ -1630,6 +1653,7 @@ void openChildInfoPipe(void);
|
||||
void closeChildInfoPipe(void);
|
||||
void sendChildInfo(int process_type);
|
||||
void receiveChildInfo(void);
|
||||
int hasActiveChildProcess();
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
@@ -1699,6 +1723,7 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec);
|
||||
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
|
||||
size_t freeMemoryGetNotCountedMemory();
|
||||
int freeMemoryIfNeeded(void);
|
||||
int freeMemoryIfNeededAndSafe(void);
|
||||
int processCommand(client *c);
|
||||
void setupSignalHandlers(void);
|
||||
struct redisCommand *lookupCommand(sds name);
|
||||
@@ -1707,6 +1732,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);
|
||||
@@ -1727,7 +1754,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);
|
||||
|
||||
@@ -615,6 +615,10 @@ void hincrbyfloatCommand(client *c) {
|
||||
}
|
||||
|
||||
value += incr;
|
||||
if (isnan(value) || isinf(value)) {
|
||||
addReplyError(c,"increment would produce NaN or Infinity");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf,sizeof(buf),value,1);
|
||||
|
||||
+2
-2
@@ -520,7 +520,7 @@ void lremCommand(client *c) {
|
||||
|
||||
if (removed) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
if (listTypeLength(subject) == 0) {
|
||||
@@ -617,7 +617,7 @@ void rpoplpushCommand(client *c) {
|
||||
* the AOF and replication channel.
|
||||
*
|
||||
* The argument 'where' is LIST_TAIL or LIST_HEAD, and indicates if the
|
||||
* 'value' element was popped fron the head (BLPOP) or tail (BRPOP) so that
|
||||
* 'value' element was popped from the head (BLPOP) or tail (BRPOP) so that
|
||||
* we can propagate the command properly.
|
||||
*
|
||||
* The function returns C_OK if we are able to serve the client, otherwise
|
||||
|
||||
+40
-22
@@ -173,9 +173,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 +202,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 +245,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. */
|
||||
@@ -492,14 +495,14 @@ void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamI
|
||||
streamEncodeID(si->start_key,start);
|
||||
} else {
|
||||
si->start_key[0] = 0;
|
||||
si->start_key[0] = 0;
|
||||
si->start_key[1] = 0;
|
||||
}
|
||||
|
||||
if (end) {
|
||||
streamEncodeID(si->end_key,end);
|
||||
} else {
|
||||
si->end_key[0] = UINT64_MAX;
|
||||
si->end_key[0] = UINT64_MAX;
|
||||
si->end_key[1] = UINT64_MAX;
|
||||
}
|
||||
|
||||
/* Seek the correct node in the radix tree. */
|
||||
@@ -1216,6 +1219,14 @@ 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;
|
||||
@@ -1732,14 +1743,17 @@ NULL
|
||||
/* Everything but the "HELP" option requires a key and group name. */
|
||||
if (c->argc >= 4) {
|
||||
o = lookupKeyWrite(c->db,c->argv[2]);
|
||||
if (o) s = o->ptr;
|
||||
if (o) {
|
||||
if (checkType(c,o,OBJ_STREAM)) return;
|
||||
s = o->ptr;
|
||||
}
|
||||
grpname = c->argv[3]->ptr;
|
||||
}
|
||||
|
||||
/* Check for missing key/group. */
|
||||
if (c->argc >= 4 && !mkstream) {
|
||||
/* At this point key must exist, or there is an error. */
|
||||
if (o == NULL) {
|
||||
if (s == NULL) {
|
||||
addReplyError(c,
|
||||
"The XGROUP subcommand requires the key to exist. "
|
||||
"Note that for CREATE you may want to use the MKSTREAM "
|
||||
@@ -1747,8 +1761,6 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkType(c,o,OBJ_STREAM)) return;
|
||||
|
||||
/* Certain subcommands require the group to exist. */
|
||||
if ((cg = streamLookupCG(s,grpname)) == NULL &&
|
||||
(!strcasecmp(opt,"SETID") ||
|
||||
@@ -1776,7 +1788,8 @@ NULL
|
||||
}
|
||||
|
||||
/* Handle the MKSTREAM option now that the command can no longer fail. */
|
||||
if (s == NULL && mkstream) {
|
||||
if (s == NULL) {
|
||||
serverAssert(mkstream);
|
||||
o = createStreamObject();
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
s = o->ptr;
|
||||
@@ -2274,8 +2287,13 @@ void xclaimCommand(client *c) {
|
||||
/* Update the consumer and idle time. */
|
||||
nack->consumer = consumer;
|
||||
nack->delivery_time = deliverytime;
|
||||
/* Set the delivery attempts counter if given. */
|
||||
if (retrycount >= 0) nack->delivery_count = retrycount;
|
||||
/* Set the delivery attempts counter if given, otherwise
|
||||
* autoincrement unless JUSTID option provided */
|
||||
if (retrycount >= 0) {
|
||||
nack->delivery_count = retrycount;
|
||||
} else if (!justid) {
|
||||
nack->delivery_count++;
|
||||
}
|
||||
/* Add the entry in the new consumer local PEL. */
|
||||
raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
|
||||
/* Send the reply for this entry. */
|
||||
|
||||
+16
-13
@@ -574,12 +574,12 @@ int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {
|
||||
switch(c[0]) {
|
||||
case '+':
|
||||
if (c[1] != '\0') return C_ERR;
|
||||
*ex = 0;
|
||||
*ex = 1;
|
||||
*dest = shared.maxstring;
|
||||
return C_OK;
|
||||
case '-':
|
||||
if (c[1] != '\0') return C_ERR;
|
||||
*ex = 0;
|
||||
*ex = 1;
|
||||
*dest = shared.minstring;
|
||||
return C_OK;
|
||||
case '(':
|
||||
@@ -652,9 +652,8 @@ int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {
|
||||
zskiplistNode *x;
|
||||
|
||||
/* Test for ranges that will always be empty. */
|
||||
if (sdscmplex(range->min,range->max) > 1 ||
|
||||
(sdscmp(range->min,range->max) == 0 &&
|
||||
(range->minex || range->maxex)))
|
||||
int cmp = sdscmplex(range->min,range->max);
|
||||
if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
|
||||
return 0;
|
||||
x = zsl->tail;
|
||||
if (x == NULL || !zslLexValueGteMin(x->ele,range))
|
||||
@@ -927,9 +926,8 @@ int zzlIsInLexRange(unsigned char *zl, zlexrangespec *range) {
|
||||
unsigned char *p;
|
||||
|
||||
/* Test for ranges that will always be empty. */
|
||||
if (sdscmplex(range->min,range->max) > 1 ||
|
||||
(sdscmp(range->min,range->max) == 0 &&
|
||||
(range->minex || range->maxex)))
|
||||
int cmp = sdscmplex(range->min,range->max);
|
||||
if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
|
||||
return 0;
|
||||
|
||||
p = ziplistIndex(zl,-2); /* Last element. */
|
||||
@@ -1359,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;
|
||||
@@ -2909,7 +2906,10 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
while (remaining) {
|
||||
if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) return;
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) {
|
||||
zslFreeLexRange(&range);
|
||||
return;
|
||||
}
|
||||
pos += 3; remaining -= 3;
|
||||
} else {
|
||||
zslFreeLexRange(&range);
|
||||
@@ -3143,7 +3143,10 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
if (countarg) {
|
||||
if (getLongFromObjectOrReply(c,countarg,&count,NULL) != C_OK)
|
||||
return;
|
||||
if (count < 0) count = 1;
|
||||
if (count <= 0) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check type and break on the first error, otherwise identify candidate. */
|
||||
|
||||
+19
-3
@@ -48,7 +48,7 @@
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
const char *string, int stringLen, int nocase)
|
||||
{
|
||||
while(patternLen) {
|
||||
while(patternLen && stringLen) {
|
||||
switch(pattern[0]) {
|
||||
case '*':
|
||||
while (pattern[1] == '*') {
|
||||
@@ -171,6 +171,22 @@ int stringmatch(const char *pattern, const char *string, int nocase) {
|
||||
return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase);
|
||||
}
|
||||
|
||||
/* Fuzz stringmatchlen() trying to crash it with bad input. */
|
||||
int stringmatchlen_fuzz_test(void) {
|
||||
char str[32];
|
||||
char pat[32];
|
||||
int cycles = 10000000;
|
||||
int total_matches = 0;
|
||||
while(cycles--) {
|
||||
int strlen = rand() % sizeof(str);
|
||||
int patlen = rand() % sizeof(pat);
|
||||
for (int j = 0; j < strlen; j++) str[j] = rand() % 128;
|
||||
for (int j = 0; j < patlen; j++) pat[j] = rand() % 128;
|
||||
total_matches += stringmatchlen(pat, patlen, str, strlen, 0);
|
||||
}
|
||||
return total_matches;
|
||||
}
|
||||
|
||||
/* Convert a string representing an amount of memory into the number of
|
||||
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
|
||||
* (1024*1024*1024).
|
||||
@@ -431,7 +447,7 @@ int string2l(const char *s, size_t slen, long *lval) {
|
||||
* a double: no spaces or other characters before or after the string
|
||||
* representing the number are accepted. */
|
||||
int string2ld(const char *s, size_t slen, long double *dp) {
|
||||
char buf[256];
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
long double value;
|
||||
char *eptr;
|
||||
|
||||
@@ -606,7 +622,7 @@ void getRandomHexChars(char *p, size_t len) {
|
||||
* already, this will be detected and handled correctly.
|
||||
*
|
||||
* The function does not try to normalize everything, but only the obvious
|
||||
* case of one or more "../" appearning at the start of "filename"
|
||||
* case of one or more "../" appearing at the start of "filename"
|
||||
* relative path. */
|
||||
sds getAbsolutePath(char *filename) {
|
||||
char cwd[1024];
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
|
||||
int stringmatch(const char *p, const char *s, int nocase);
|
||||
int stringmatchlen_fuzz_test(void);
|
||||
long long memtoll(const char *p, int *err);
|
||||
uint32_t digits10(uint64_t v);
|
||||
uint32_t sdigits10(int64_t v);
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "5.0.2"
|
||||
#define REDIS_VERSION "5.0.7"
|
||||
|
||||
+1
-1
@@ -576,7 +576,7 @@ void zipEntry(unsigned char *p, zlentry *e) {
|
||||
|
||||
/* Create a new empty ziplist. */
|
||||
unsigned char *ziplistNew(void) {
|
||||
unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
|
||||
unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
|
||||
unsigned char *zl = zmalloc(bytes);
|
||||
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
|
||||
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
|
||||
|
||||
@@ -323,6 +323,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,
|
||||
|
||||
@@ -166,12 +166,15 @@ start_server {} {
|
||||
# Pick a random slave
|
||||
set slave_id [expr {($master_id+1)%5}]
|
||||
set sync_count [status $R($master_id) sync_full]
|
||||
set sync_partial [status $R($master_id) sync_partial_ok]
|
||||
catch {
|
||||
$R($slave_id) config rewrite
|
||||
$R($slave_id) debug restart
|
||||
}
|
||||
# note: just waiting for connected_slaves==4 has a race condition since
|
||||
# we might do the check before the master realized that the slave disconnected
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R($master_id) connected_slaves] == 4
|
||||
[status $R($master_id) sync_partial_ok] == $sync_partial + 1
|
||||
} else {
|
||||
fail "Replica not reconnecting"
|
||||
}
|
||||
|
||||
@@ -79,6 +79,32 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
|
||||
stop_bg_complex_data $load_handle0
|
||||
stop_bg_complex_data $load_handle1
|
||||
stop_bg_complex_data $load_handle2
|
||||
|
||||
# Wait for the slave to reach the "online"
|
||||
# state from the POV of the master.
|
||||
set retry 5000
|
||||
while {$retry} {
|
||||
set info [$master info]
|
||||
if {[string match {*slave0:*state=online*} $info]} {
|
||||
break
|
||||
} else {
|
||||
incr retry -1
|
||||
after 100
|
||||
}
|
||||
}
|
||||
if {$retry == 0} {
|
||||
error "assertion:Slave not correctly synchronized"
|
||||
}
|
||||
|
||||
# Wait that slave acknowledge it is online so
|
||||
# we are sure that DBSIZE and DEBUG DIGEST will not
|
||||
# fail because of timing issues. (-LOADING error)
|
||||
wait_for_condition 5000 100 {
|
||||
[lindex [$slave role] 3] eq {connected}
|
||||
} else {
|
||||
fail "Slave still not connected after some time"
|
||||
}
|
||||
|
||||
set retry 10
|
||||
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
|
||||
{
|
||||
|
||||
@@ -275,7 +275,6 @@ start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
test "Master stream is correctly processed while the replica has a script in -BUSY state" {
|
||||
set slave [srv 0 client]
|
||||
puts [srv 0 port]
|
||||
$slave config set lua-time-limit 500
|
||||
$slave slaveof $master_host $master_port
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
# find the OS
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
# Compile flags for linux / osx
|
||||
ifeq ($(uname_S),Linux)
|
||||
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_LDFLAGS ?= -shared
|
||||
else
|
||||
SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
|
||||
endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
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,149 @@
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "redismodule.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static RedisModuleString *log_key_name;
|
||||
|
||||
static const char log_command_name[] = "commandfilter.log";
|
||||
static const char ping_command_name[] = "commandfilter.ping";
|
||||
static const char unregister_command_name[] = "commandfilter.unregister";
|
||||
static int in_log_command = 0;
|
||||
|
||||
static RedisModuleCommandFilter *filter = NULL;
|
||||
|
||||
int CommandFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx,
|
||||
RedisModule_UnregisterCommandFilter(ctx, filter));
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int CommandFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "c", "@log");
|
||||
if (reply) {
|
||||
RedisModule_ReplyWithCallReply(ctx, reply);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} else {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int CommandFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
RedisModuleString *s = RedisModule_CreateString(ctx, "", 0);
|
||||
|
||||
int i;
|
||||
for (i = 1; i < argc; i++) {
|
||||
size_t arglen;
|
||||
const char *arg = RedisModule_StringPtrLen(argv[i], &arglen);
|
||||
|
||||
if (i > 1) RedisModule_StringAppendBuffer(ctx, s, " ", 1);
|
||||
RedisModule_StringAppendBuffer(ctx, s, arg, arglen);
|
||||
}
|
||||
|
||||
RedisModuleKey *log = RedisModule_OpenKey(ctx, log_key_name, REDISMODULE_WRITE|REDISMODULE_READ);
|
||||
RedisModule_ListPush(log, REDISMODULE_LIST_HEAD, s);
|
||||
RedisModule_CloseKey(log);
|
||||
RedisModule_FreeString(ctx, s);
|
||||
|
||||
in_log_command = 1;
|
||||
|
||||
size_t cmdlen;
|
||||
const char *cmdname = RedisModule_StringPtrLen(argv[1], &cmdlen);
|
||||
RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", &argv[2], argc - 2);
|
||||
if (reply) {
|
||||
RedisModule_ReplyWithCallReply(ctx, reply);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} else {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
|
||||
}
|
||||
|
||||
in_log_command = 0;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void CommandFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
|
||||
{
|
||||
if (in_log_command) return; /* don't process our own RM_Call() from CommandFilter_LogCommand() */
|
||||
|
||||
/* Fun manipulations:
|
||||
* - Remove @delme
|
||||
* - Replace @replaceme
|
||||
* - Append @insertbefore or @insertafter
|
||||
* - Prefix with Log command if @log encounterd
|
||||
*/
|
||||
int log = 0;
|
||||
int pos = 0;
|
||||
while (pos < RedisModule_CommandFilterArgsCount(filter)) {
|
||||
const RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, pos);
|
||||
size_t arg_len;
|
||||
const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len);
|
||||
|
||||
if (arg_len == 6 && !memcmp(arg_str, "@delme", 6)) {
|
||||
RedisModule_CommandFilterArgDelete(filter, pos);
|
||||
continue;
|
||||
}
|
||||
if (arg_len == 10 && !memcmp(arg_str, "@replaceme", 10)) {
|
||||
RedisModule_CommandFilterArgReplace(filter, pos,
|
||||
RedisModule_CreateString(NULL, "--replaced--", 12));
|
||||
} else if (arg_len == 13 && !memcmp(arg_str, "@insertbefore", 13)) {
|
||||
RedisModule_CommandFilterArgInsert(filter, pos,
|
||||
RedisModule_CreateString(NULL, "--inserted-before--", 19));
|
||||
pos++;
|
||||
} else if (arg_len == 12 && !memcmp(arg_str, "@insertafter", 12)) {
|
||||
RedisModule_CommandFilterArgInsert(filter, pos + 1,
|
||||
RedisModule_CreateString(NULL, "--inserted-after--", 18));
|
||||
pos++;
|
||||
} else if (arg_len == 4 && !memcmp(arg_str, "@log", 4)) {
|
||||
log = 1;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (log) RedisModule_CommandFilterArgInsert(filter, 0,
|
||||
RedisModule_CreateString(NULL, log_command_name, sizeof(log_command_name)-1));
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (RedisModule_Init(ctx,"commandfilter",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (argc != 2) {
|
||||
RedisModule_Log(ctx, "warning", "Log key name not specified");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
long long noself = 0;
|
||||
log_key_name = RedisModule_CreateStringFromString(ctx, argv[0]);
|
||||
RedisModule_StringToLongLong(argv[1], &noself);
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,log_command_name,
|
||||
CommandFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,ping_command_name,
|
||||
CommandFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,unregister_command_name,
|
||||
CommandFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if ((filter = RedisModule_RegisterCommandFilter(ctx, CommandFilter_CommandFilter,
|
||||
noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
|
||||
== NULL) return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -91,6 +91,14 @@ proc wait_for_sync r {
|
||||
}
|
||||
}
|
||||
|
||||
proc wait_for_ofs_sync {r1 r2} {
|
||||
wait_for_condition 50 100 {
|
||||
[status $r1 master_repl_offset] eq [status $r2 master_repl_offset]
|
||||
} else {
|
||||
fail "replica didn't sync in time"
|
||||
}
|
||||
}
|
||||
|
||||
# Random integer between 0 and max (excluded).
|
||||
proc randomInt {max} {
|
||||
expr {int(rand()*$max)}
|
||||
|
||||
@@ -502,7 +502,7 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--only}} {
|
||||
lappend ::only_tests $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--skiptill}} {
|
||||
} elseif {$opt eq {--skip-till}} {
|
||||
set ::skip_till $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--list-tests}} {
|
||||
|
||||
@@ -115,6 +115,34 @@ start_server {tags {"hll"}} {
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Fuzzing dense/sparse encoding: Redis should always detect errors} {
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
r del hll
|
||||
set items {}
|
||||
set numitems [randomInt 3000]
|
||||
for {set i 0} {$i < $numitems} {incr i} {
|
||||
lappend items [expr {rand()}]
|
||||
}
|
||||
r pfadd hll {*}$items
|
||||
|
||||
# Corrupt it in some random way.
|
||||
for {set i 0} {$i < 5} {incr i} {
|
||||
set len [r strlen hll]
|
||||
set pos [randomInt $len]
|
||||
set byte [randstring 1 1 binary]
|
||||
r setrange hll $pos $byte
|
||||
# Don't modify more bytes 50% of times
|
||||
if {rand() < 0.5} break
|
||||
}
|
||||
|
||||
# Use the hyperloglog to check if it crashes
|
||||
# Redis in some way.
|
||||
catch {
|
||||
r pfcount hll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {PFADD, PFCOUNT, PFMERGE type checking works} {
|
||||
r set foo bar
|
||||
catch {r pfadd foo 1} e
|
||||
|
||||
@@ -161,7 +161,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
|
||||
}
|
||||
|
||||
# make sure master doesn't disconnect slave because of timeout
|
||||
$master config set repl-timeout 300 ;# 5 minutes
|
||||
$master config set repl-timeout 1200 ;# 20 minutes (for valgrind and slow machines)
|
||||
$master config set maxmemory-policy allkeys-random
|
||||
$master config set client-output-buffer-limit "replica 100000000 100000000 300"
|
||||
$master config set repl-backlog-size [expr {10*1024}]
|
||||
@@ -212,7 +212,8 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
|
||||
|
||||
assert {[$master dbsize] == 100}
|
||||
assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers
|
||||
assert {$delta < 50*1024 && $delta > -50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
set delta_max [expr {$cmd_count / 2}] ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
assert {$delta < $delta_max && $delta > -$delta_max}
|
||||
|
||||
$master client kill type slave
|
||||
set killed_used [s -1 used_memory]
|
||||
@@ -221,7 +222,7 @@ proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline}
|
||||
set killed_used_no_repl [expr {$killed_used - $killed_mem_not_counted_for_evict}]
|
||||
set delta_no_repl [expr {$killed_used_no_repl - $used_no_repl}]
|
||||
assert {$killed_slave_buf == 0}
|
||||
assert {$delta_no_repl > -50*1024 && $delta_no_repl < 50*1024} ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
|
||||
assert {$delta_no_repl > -$delta_max && $delta_no_repl < $delta_max}
|
||||
|
||||
}
|
||||
# unfreeze slave process (after the 'test' succeeded or failed, but before we attempt to terminate the server
|
||||
|
||||
@@ -90,6 +90,7 @@ start_server {tags {"defrag"}} {
|
||||
test "Active defrag big keys" {
|
||||
r flushdb
|
||||
r config resetstat
|
||||
r config set save "" ;# prevent bgsave from interfereing with save below
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-max-scan-fields 1000
|
||||
r config set active-defrag-threshold-lower 5
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
set testmodule [file normalize tests/modules/commandfilter.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule log-key 0
|
||||
|
||||
test {Command Filter handles redirected commands} {
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} "{set mykey @log}"
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgDelete} {
|
||||
r rpush mylist elem1 @delme elem2
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 elem2}
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgInsert} {
|
||||
r del mylist
|
||||
r rpush mylist elem1 @insertbefore elem2 @insertafter elem3
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3}
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgReplace} {
|
||||
r del mylist
|
||||
r rpush mylist elem1 @replaceme elem2
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 --replaced-- elem2}
|
||||
|
||||
test {Command Filter applies on RM_Call() commands} {
|
||||
r del log-key
|
||||
r commandfilter.ping
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter applies on Lua redis.call()} {
|
||||
r del log-key
|
||||
r eval "redis.call('ping', '@log')" 0
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter applies on Lua redis.call() that calls a module} {
|
||||
r del log-key
|
||||
r eval "redis.call('commandfilter.ping')" 0
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter is unregistered implicitly on module unload} {
|
||||
r del log-key
|
||||
r module unload commandfilter
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} {}
|
||||
|
||||
r module load $testmodule log-key 0
|
||||
|
||||
test {Command Filter unregister works as expected} {
|
||||
# Validate reloading succeeded
|
||||
r del log-key
|
||||
r set mykey @log
|
||||
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
|
||||
|
||||
# Unregister
|
||||
r commandfilter.unregister
|
||||
r del log-key
|
||||
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} {}
|
||||
|
||||
r module unload commandfilter
|
||||
r module load $testmodule log-key 1
|
||||
|
||||
test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} {
|
||||
r set mykey @log
|
||||
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
|
||||
|
||||
r del log-key
|
||||
r commandfilter.ping
|
||||
assert_equal {} [r lrange log-key 0 -1]
|
||||
|
||||
r eval "redis.call('commandfilter.ping')" 0
|
||||
assert_equal {} [r lrange log-key 0 -1]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -80,9 +80,11 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} {
|
||||
}
|
||||
|
||||
test {SLOWLOG - can be disabled} {
|
||||
r config set slowlog-max-len 1
|
||||
r config set slowlog-log-slower-than 1
|
||||
r slowlog reset
|
||||
assert_equal [r slowlog len] 1
|
||||
r debug sleep 0.2
|
||||
assert_equal [r slowlog len] 1
|
||||
r config set slowlog-log-slower-than -1
|
||||
r slowlog reset
|
||||
r debug sleep 0.2
|
||||
|
||||
@@ -147,6 +147,97 @@ start_server {
|
||||
assert {[lindex $res 0 1 1] == {2-0 {field1 B}}}
|
||||
}
|
||||
|
||||
test {XCLAIM can claim PEL items from another consumer} {
|
||||
# Add 3 items into the stream, and create a consumer group
|
||||
r del mystream
|
||||
set id1 [r XADD mystream * a 1]
|
||||
set id2 [r XADD mystream * b 2]
|
||||
set id3 [r XADD mystream * c 3]
|
||||
r XGROUP CREATE mystream mygroup 0
|
||||
|
||||
# Client 1 reads item 1 from the stream without acknowledgements.
|
||||
# Client 2 then claims pending item 1 from the PEL of client 1
|
||||
set reply [
|
||||
r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
|
||||
]
|
||||
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
||||
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
||||
r debug sleep 0.2
|
||||
set reply [
|
||||
r XCLAIM mystream mygroup client2 10 $id1
|
||||
]
|
||||
assert {[llength [lindex $reply 0 1]] == 2}
|
||||
assert {[lindex $reply 0 1] eq {a 1}}
|
||||
|
||||
# Client 1 reads another 2 items from stream
|
||||
r XREADGROUP GROUP mygroup client1 count 2 STREAMS mystream >
|
||||
r debug sleep 0.2
|
||||
|
||||
# Delete item 2 from the stream. Now client 1 has PEL that contains
|
||||
# only item 3. Try to use client 2 to claim the deleted item 2
|
||||
# from the PEL of client 1, this should return nil
|
||||
r XDEL mystream $id2
|
||||
set reply [
|
||||
r XCLAIM mystream mygroup client2 10 $id2
|
||||
]
|
||||
assert {[llength $reply] == 1}
|
||||
assert_equal "" [lindex $reply 0]
|
||||
|
||||
# Delete item 3 from the stream. Now client 1 has PEL that is empty.
|
||||
# Try to use client 2 to claim the deleted item 3 from the PEL
|
||||
# of client 1, this should return nil
|
||||
r debug sleep 0.2
|
||||
r XDEL mystream $id3
|
||||
set reply [
|
||||
r XCLAIM mystream mygroup client2 10 $id3
|
||||
]
|
||||
assert {[llength $reply] == 1}
|
||||
assert_equal "" [lindex $reply 0]
|
||||
}
|
||||
|
||||
test {XCLAIM without JUSTID increments delivery count} {
|
||||
# Add 3 items into the stream, and create a consumer group
|
||||
r del mystream
|
||||
set id1 [r XADD mystream * a 1]
|
||||
set id2 [r XADD mystream * b 2]
|
||||
set id3 [r XADD mystream * c 3]
|
||||
r XGROUP CREATE mystream mygroup 0
|
||||
|
||||
# Client 1 reads item 1 from the stream without acknowledgements.
|
||||
# Client 2 then claims pending item 1 from the PEL of client 1
|
||||
set reply [
|
||||
r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
|
||||
]
|
||||
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
||||
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
||||
r debug sleep 0.2
|
||||
set reply [
|
||||
r XCLAIM mystream mygroup client2 10 $id1
|
||||
]
|
||||
assert {[llength [lindex $reply 0 1]] == 2}
|
||||
assert {[lindex $reply 0 1] eq {a 1}}
|
||||
|
||||
set reply [
|
||||
r XPENDING mystream mygroup - + 10
|
||||
]
|
||||
assert {[llength [lindex $reply 0]] == 4}
|
||||
assert {[lindex $reply 0 3] == 2}
|
||||
|
||||
# Client 3 then claims pending item 1 from the PEL of client 2 using JUSTID
|
||||
r debug sleep 0.2
|
||||
set reply [
|
||||
r XCLAIM mystream mygroup client3 10 $id1 JUSTID
|
||||
]
|
||||
assert {[llength $reply] == 1}
|
||||
assert {[lindex $reply 0] eq $id1}
|
||||
|
||||
set reply [
|
||||
r XPENDING mystream mygroup - + 10
|
||||
]
|
||||
assert {[llength [lindex $reply 0]] == 4}
|
||||
assert {[lindex $reply 0 3] == 2}
|
||||
}
|
||||
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
@@ -183,6 +274,8 @@ start_server {
|
||||
}
|
||||
}
|
||||
|
||||
wait_for_ofs_sync $master $slave
|
||||
|
||||
# Turn slave into master
|
||||
$slave slaveof no one
|
||||
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -355,12 +367,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}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -388,7 +388,7 @@ start_server {tags {"zset"}} {
|
||||
0 omega}
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
|
||||
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZLEXCOUNT basics" {
|
||||
create_default_lex_zset
|
||||
|
||||
# inclusive range
|
||||
@@ -416,6 +416,22 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
|
||||
assert_equal {} [r zrevrangebylex zset (hill (omega]
|
||||
}
|
||||
|
||||
test "ZLEXCOUNT advanced" {
|
||||
create_default_lex_zset
|
||||
|
||||
assert_equal 9 [r zlexcount zset - +]
|
||||
assert_equal 0 [r zlexcount zset + -]
|
||||
assert_equal 0 [r zlexcount zset + \[c]
|
||||
assert_equal 0 [r zlexcount zset \[c -]
|
||||
assert_equal 8 [r zlexcount zset \[bar +]
|
||||
assert_equal 5 [r zlexcount zset \[bar \[foo]
|
||||
assert_equal 4 [r zlexcount zset \[bar (foo]
|
||||
assert_equal 4 [r zlexcount zset (bar \[foo]
|
||||
assert_equal 3 [r zlexcount zset (bar (foo]
|
||||
assert_equal 5 [r zlexcount zset - (foo]
|
||||
assert_equal 1 [r zlexcount zset (maxstring +]
|
||||
}
|
||||
|
||||
test "ZRANGEBYSLEX with LIMIT" {
|
||||
create_default_lex_zset
|
||||
|
||||
+2
-1
@@ -21,8 +21,9 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
srand(time(NULL));
|
||||
char *filename = argv[1];
|
||||
cycles = atoi(argv[2]);
|
||||
fd = open("dump.rdb",O_RDWR);
|
||||
fd = open(filename,O_RDWR);
|
||||
if (fd == -1) {
|
||||
perror("open");
|
||||
exit(1);
|
||||
|
||||
Reference in New Issue
Block a user