Compare commits

...
256 Commits
Author SHA1 Message Date
antirez 8ff874d2b2 2.2.15 release notes 2011-10-17 10:57:18 +02:00
antirez f97b01d36a version bumped to 2.2.15 2011-10-17 10:56:12 +02:00
antirez 991eee4d01 FLUSHALL now prevents rdbSave() from resetting the dirty counter, so that the command will get replicated and put inside the AOF. This fixes issue #142 2011-10-17 10:55:46 +02:00
antirez 74b77ff8a6 FLUSHALL will only perform a blocking SAVE if RDB persistence is configured. 2011-10-17 10:55:37 +02:00
antirez 15da88c8dc Compilation fixed on OpenBSD making sure that _XOPEN_SOURCE is set to 700 in fmacros.h 2011-09-29 10:20:36 +02:00
Juri M. Vainonenandantirez ee2f20dfde fixed a small bug that caused redis-cli to segfault when given single numeric parameter greater that zero. 2011-09-21 22:33:13 +02:00
antirez 0dfc5578bf Version bumped to 2.2.14, Changelog updated. 2011-09-21 18:12:56 +02:00
antirez ee9f3679a9 Fixed a bug in the average latency metering of redis-cli --latency 2011-09-21 18:10:07 +02:00
antirez 28de624c70 Implemented --latency in redis-cli 2011-09-21 18:09:46 +02:00
Pieter Noordhuisandantirez 5d4675979d Fix AOF race that may duplicate commands 2011-09-16 11:22:29 +02:00
antirez 9e087a298d Optimize LRANGE to scan the list starting from the head or the tail in order to traverse the minimal number of elements. Thanks to Didier Spezia for noticing the problem and providing a patch. 2011-09-14 15:17:09 +02:00
antirez 8ac3c86664 Don't replicate/AOF SAVE 2011-09-14 14:54:08 +02:00
antirez a8ed663376 Version bumped to 2.2.13, changelog updated. 2011-09-12 15:49:20 +02:00
antirez 61f57b6a8f Remove the write handler only if there are no longer objects in the output queue AND if the static buffer is empty. This bug was the cause of a possible server-stop-responding-to-client bug under some specific work load. Thanks to Pieter Noordhuis for spotting and fixing it. 2011-09-12 11:05:48 +02:00
antirez 24a1580df5 Fix for 2.2 TCMALLOC build, thanks to Pieter Noordhuis for the patch. 2011-09-12 11:01:55 +02:00
antirez 4793a2c3c3 fixed typos in the comments of rpoplpushHandlePush() 2011-09-12 10:06:00 +02:00
Hampus Wessmanandantirez 1d05b53fcc Fix crash when chaining brpoplpush with other blocking commands. 2011-09-12 10:05:58 +02:00
Pieter Noordhuis e1f01c9b28 Fix link order for ld --as-needed (thanks to Nigel Babu) 2011-07-28 10:42:57 +02:00
antirez c1b270127c Merge remote-tracking branch 'origin/2.2' into 2.2 2011-07-27 16:37:45 +02:00
Pieter Noordhuisandantirez 62e6f6c29a Fix adding bulk reply when getcwd fails 2011-07-27 16:22:58 +02:00
antirez 5960ac9dec Redis 2.2.12 2011-07-22 12:22:26 +02:00
antirez ec7bbadce9 Close the listening sockets. Apparently this allows faster restarts. 2011-07-22 12:12:55 +02:00
antirez 063923de1c prepareForShutdown() fixed for correctness regarding what to do with AOF and RDB persistence on exit. 2011-07-22 12:03:24 +02:00
Hampus Wessmanandantirez aa96ebec89 Add test for incorrect expiration when loading AOF. 2011-07-15 18:15:27 +02:00
antirez 563f4cfee5 force VM usage when doing the VM test in 2.2, adding the really-use-vm config option. 2011-07-15 18:13:43 +02:00
antirez e8108591e4 Warn the user that will try to enable VM that VM sucks. But still allows him to enable VM with a special option. 2011-07-15 18:11:00 +02:00
antirez eed4ec4664 Warn the user in redis.conf that VM is deprecated and discouraged 2011-07-15 18:10:20 +02:00
antirez 9702c92c4f Fixed another merge typo 2011-07-15 18:04:41 +02:00
antirez 3fee7e3013 removed a second copy of rewriteClientCommandVector put inside the source code for a merge error 2011-07-15 18:02:45 +02:00
antirez 891f9196fc Manual merge after cherry-pick 2011-07-15 18:00:24 +02:00
antirez 59aecb3a6c Take a pointer to the relevant entry of the command table in the client structure. This is generally a more sounding design, simplifies a few functions prototype, and as a side effect fixes a bug related to the conversion of EXPIRE -1 to DEL: before of this fix Redis tried to convert it into an EXPIREAT in the AOF code, regardless of our rewrite of the command. 2011-07-15 18:00:03 +02:00
antirez b8082ae7ba don't process EXPIRE with negative TTL or EXPIREAT with time in the past if we are a slave too (see http://groups.google.com/group/redis-db/browse_thread/thread/5a931fefb88b16d5). Also propagate it as DEL. 2011-07-15 17:59:27 +02:00
antirez d30dafe7f4 use the new rewriteClientCommandVector() function for SPOP -> SREM replication translation as well. 2011-07-15 17:58:22 +02:00
Hampus Wessmanandantirez a906670e2d Don't expire keys while loading AOF.
They will be expired (and a DEL will be logged) after the loading is done
instead.
2011-07-15 17:48:22 +02:00
antirez d3203c16f5 Fixed a problem with AOF when it is stopped via CONFIG SET appendonly no. We used to kill the wrong child... 2011-07-07 13:24:44 +02:00
antirez 2e1adff83e Fixed typo in redis.conf related to slow log parameters 2011-07-03 15:50:21 +02:00
antirez 194058931f childrens -> children in INFO output field names. This is a backward incompatible change, but really a minimal one. 2011-07-02 10:52:41 +02:00
antirez 5a7bfb8a84 colorized make for 2.2 as well 2011-07-02 10:50:13 +02:00
antirez aaa7fe1551 no more allocation stats info in INFO, useless now that we have jemalloc. 2011-07-02 10:33:52 +02:00
antirez 33d2761bc0 ustime() backported from unstable, used by slow log 2011-07-01 15:47:48 +02:00
antirez ad6347b74e Slowlog backported to 2.2 2011-07-01 15:45:36 +02:00
antirez d3b5989148 Partial fix for a problem with AOF and BRPOP. We have at least a single evidence of an AOF that ended with a BRPOP call against an empty list, causing later issues at run time. Now the problem is detected while loading the AOF at startup, even if it is still to understand how the corrupted AOF was generated. 2011-06-29 16:10:28 +02:00
antirez a2d68e6524 Redis 2.2.11 2011-06-22 22:37:21 +02:00
antirez 25e805b417 Use a safe iterator while saving the DB, since the getExpire() function will access the iterating dictionary. 2011-06-17 15:41:10 +02:00
antirez 4ec8b732b7 Redis 2.2.10 2011-06-14 17:32:49 +02:00
antirez 046b4f66ef redis-cli updated to the same version as the 2.4 branch 2011-06-14 17:28:01 +02:00
antirez 5d91926304 Fix for issue 566, fixed in a more long term way into the 2.4 branch 2011-06-14 17:19:25 +02:00
Pieter Noordhuisandantirez 5afce9b6ea SETNX regression tests. 2011-06-14 17:13:08 +02:00
antirez 0df2e5b9d5 Fixed init script bug, thanks to Henrik Westphal 2011-06-14 17:12:14 +02:00
antirez 0b4eb5fd21 Better init script 2011-06-14 13:49:02 +02:00
Pieter Noordhuis b13216a8c7 Work with make v3.80 2011-06-13 09:42:03 +02:00
antirez 25a585ad42 Changelog typo fixed 2011-06-09 22:03:18 +02:00
antirez 7d8c021a9a Redis 2.2.9 2011-06-09 19:08:22 +02:00
antirez be87e76906 Merge remote branch 'origin/2.2' into 2.2 2011-06-09 19:04:48 +02:00
antirez 09d9879963 AOF log rewriting in slave performed after a successful sych with the master was misplaced. Now it happens at the right time. 2011-06-09 19:04:31 +02:00
antirez dfc74051ce test for the BRPOPLPUSH issue 561 related issues 2011-05-30 17:16:33 +02:00
antirez 196fc32b77 use the new rewriteClientCommandVector() function for SPOP -> SREM replication translation as well. 2011-05-27 15:39:31 +02:00
antirez b190b0c98f modified the BRPOPLPUSH target field cleanup strategy to fix it the proper way. 2011-05-27 15:01:20 +02:00
antirez d7061f8137 Removed a leak in the BRPOPLPUSH unrelated to issue 561 2011-05-27 14:09:50 +02:00
antirez 20867e8009 Fix for issue 516, rewriting the command vector to correctly repliate BRPOPLPUSH. Still to test everything, especially edge cases 2011-05-26 13:49:03 +02:00
antirez 10e987c5fd Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-05-25 12:29:23 +02:00
antirez cc958ccf42 Fix for Pub/Sub system, introduced in Redis 2.2.6 with the new copy-on-write safe iterator semantics. In the hope this is the last bug I introduced this way. 2011-05-25 12:29:14 +02:00
antirez ec279203df Redis 2.2.8 2011-05-24 16:34:53 +02:00
antirez 480a2e73cf Comment typo fixed 2011-05-24 10:43:31 +02:00
antirez f447a7ebb4 ZUNIONSTORE new iterator API fix backported into 2.2 2011-05-21 20:34:59 +02:00
antirez 748a2da3e8 Print version info before running the test 2011-05-20 11:44:09 +02:00
antirez b02e81be07 Fixed SINTER[STORE] problem related to the new copy on write safe iterator 2011-05-15 12:15:54 +02:00
antirez 430719ca53 Fixed bug introduced with the copy-on-write friendly iteartor 2011-05-13 12:53:07 +02:00
antirez cc8a0f898b Fix for DEBUG DIGEST, key may expire on lookup 2011-05-13 11:42:02 +02:00
antirez cd128d2882 replication with expire test modified to produce no or less false failures 2011-05-12 20:15:13 +02:00
antirez c717adbc2e regression for zinterstore bug 2011-05-12 20:14:15 +02:00
Pieter Noordhuisandantirez 1ffa5d73ed Convert intset encoded set before Z*STORE 2011-05-12 16:18:43 +02:00
antirez a7fa2baf06 Redis 2.2.7 2011-05-11 10:40:24 +02:00
antirez f7aef5241b removed assert causing an illegal memory access. This was responsible of crashes during BLPOP and other list blocking operations. 2011-05-11 09:49:23 +02:00
antirez c86a4f9102 use safe Dict iteration in DEBUG DIGEST 2011-05-10 11:04:30 +02:00
antirez 2e638590ad Introduced a safe iterator interface that can be used to iterate while accessing the dictionary at the same time. Now the default interface is consireded unsafe and should be used only with dictNext() 2011-05-10 11:04:03 +02:00
antirez 3554f09ddc Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-05-07 12:10:47 +02:00
antirez ccab83e729 when Redis fails accepting a new connection reports the error at WARNING and not VERBOSE error level. Thanks to offby1 for proposing this in the Redis mailing list. #backport-candidate 2011-05-07 12:10:39 +02:00
antirez 97ddfbbfc3 Redis version 2.2.6 2011-05-05 15:56:47 +02:00
Pieter Noordhuisandantirez 50e50d6a25 Fix ziplist regression and update stresser 2011-05-05 15:17:49 +02:00
antirez 9d665825d9 Fixed a bug with replication where SLAVEOF NO ONE caused a slave to close the connection with its slaves 2011-04-29 14:18:33 +02:00
antirez 19b55f8ebc Redis 2.2.5 2011-04-22 16:25:13 +02:00
Pieter Noordhuis bf3692797d Use correct argc/argv for cleanup when loading AOF 2011-04-22 09:41:07 +02:00
Pieter Noordhuis da06854477 Reformat AOF tests 2011-04-22 09:40:10 +02:00
antirez 38de9362b2 Revert "tests for variadic list push"
This reverts commit 1b905277bb.
Moving all the new stuff in 2.4 branch.
2011-04-15 17:41:32 +02:00
antirez 7600994937 Revert "variadic LPUSH/RPUSH backported to 2.2"
This reverts commit c138dc7da4.
Moving all the new stuff in 2.4 branch.
2011-04-15 17:41:07 +02:00
antirez 6a9764d183 Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-04-15 17:28:03 +02:00
antirez c138dc7da4 variadic LPUSH/RPUSH backported to 2.2 2011-04-15 17:27:20 +02:00
antirez 1b905277bb tests for variadic list push 2011-04-15 17:15:59 +02:00
antirez 2b886275e9 Redis 2.2.4 2011-04-06 15:39:54 +02:00
antirez 93db956e06 make sure that OBJECT ENCODING returns skiplist for sorted sets, and not raw, so that once we will merge specially encoded sorted sets everything will make sense. 2011-04-06 15:38:08 +02:00
antirez 27fc6199ca CHANGELOG updated 2011-04-06 12:46:39 +02:00
antirez 0b01578d2e Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-04-06 12:23:44 +02:00
antirez 920c45b818 version is now 2.2.3 2011-04-06 12:23:16 +02:00
antirez cf6c3f4b04 OBJECT command implemented 2011-04-06 12:22:09 +02:00
antirez fb90934c47 fixed memory leak introduced with the previous commit. Many thanks to Pieter Noordhuis for spotting it in no time 2011-03-31 19:54:08 +02:00
antirez 207ca3cedd Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-03-31 16:45:27 +02:00
antirez 97d3b7dc8d Fixed issue #503. MONITOR + QUIT could crash the server, there are actually other interactions that could have the same effect (for instance Pub/Sub). 2011-03-31 16:45:05 +02:00
antirez abdbfc14c0 Fixed sdssplitargs() handling of hex-style escapes. 2011-03-22 22:49:33 +01:00
antirez de5d4be994 Merge remote branch 'pietern/2.2-sigs' into 2.2 2011-03-07 12:21:41 +01:00
Pieter Noordhuis 69bfdcf7d4 Show database number in cli prompt when non-zero 2011-03-06 21:10:50 +01:00
Pieter Noordhuis 699465c31e Store SELECTed database for reconnect (issue #468) 2011-03-06 20:53:13 +01:00
Pieter Noordhuis 45332cc47b Minor memory leak in redis-cli (issue #464) 2011-03-06 20:53:12 +01:00
Pieter Noordhuis 2d2c17b075 Save RDB on SIGTERM (see issue #471) 2011-03-06 17:49:22 +01:00
antirez 048c0f0f37 RELEASE NOTES updated 2011-03-04 16:31:20 +01:00
antirez 3eb3cc9da4 version is now 2.2.2 2011-03-04 16:29:38 +01:00
antirez 74f6ee8dd5 Fixed return value of GETRANGE / SUBSTR 2011-03-04 16:23:18 +01:00
antirez 7a55d72f28 AOF file descriptor leak fixed 2011-03-04 16:14:09 +01:00
antirez 4e4b3b943c Redis 2.1.1, release notes updated 2011-02-23 15:48:24 +01:00
Pieter Noordhuis 61e2547675 Restore argc/argv in EXEC after command is executed 2011-02-23 09:39:29 +01:00
antirez 595b5974f8 fixed client-libraries/README to reflect the new site path instead of google code 2011-02-22 18:22:00 +01:00
antirez 58418d7c3e linenoise updated 2011-02-22 17:40:33 +01:00
antirez c02bda324f fixed color support for test suite. Now colors are shown as long as there is xterm somewhere inside the TERM env var 2011-02-22 16:40:24 +01:00
Paul Gideon Dannandantirez 1afef16d88 Allow port to be 0 in configuration, in order to allow disabling of TCP. 2011-02-22 12:12:30 +01:00
antirez 7377aacd84 seed the random number generator at startup 2011-02-22 00:01:48 +01:00
antirez eece2d528c suppress a Linux warning, for 2.2 sake 2011-02-21 17:50:54 +01:00
antirez eb5aeaf138 check return value of getcwd() 2011-02-21 17:41:25 +01:00
antirez 1a6281f52f version bumped to 2.2.0 and release notes added 2011-02-17 17:15:52 +01:00
antirez c2571b314a CONFIG SET/GET for all the special encoding parameters of sets, lists, hashes 2011-02-17 12:21:22 +01:00
Pieter Noordhuis 97aeda9828 Morph SPOP to SREM for correct behavior on log replay/replication 2011-02-16 00:24:54 +01:00
antirez ca9d961073 CONFIG GET/SET dir implemented 2011-02-14 02:51:49 +01:00
antirez 2446fbb3bb Merge remote branch 'pietern/2.2-brpoplpush' into 2.2 2011-02-07 15:27:45 +01:00
antirez 970e813be2 Fix for the previous port 0 patch, thanks to Pieter for noticing the error 2011-02-07 13:43:55 +01:00
antirez a36d524562 If port zero is specified, Redis will not listen for TCP connections 2011-02-07 12:52:40 +01:00
Pieter Noordhuis bbaf76ea2f Clarify comment 2011-01-31 16:50:17 +01:00
Pieter Noordhuis 669f302e16 Reply with single null bulk for unsuccesful BRPOPLPUSH 2011-01-31 16:50:00 +01:00
antirez 71791e7a8e propagate key eviction to slaves and AOF, like for expires, so that replicas and AOFs are always consistent even in maxmemory mode. 2011-01-27 16:53:23 +01:00
antirez 2f91d7ada4 add tarball script added 2011-01-25 10:45:53 +01:00
antirez 5ef8f9531d other minor README changes 2011-01-25 10:26:00 +01:00
antirez 3ce30a6906 README updated 2011-01-25 10:25:05 +01:00
antirez 65e79a8272 release noted updated, version bumped to 2.1.12 (2.2.0 RC4) 2011-01-25 10:21:13 +01:00
antirez ef8bc13d0d Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-01-24 10:07:18 +01:00
antirez 0ffeadd290 portability fix in redis benchmark, using zu format specified with size_t instead of lu 2011-01-24 10:06:09 +01:00
antirez db0e263b42 Fixed issue #435 and at the same time introduced explicit ping in the master-slave channel that will detect a blocked master or a broken even if apparently connected TCP link. 2011-01-20 18:02:51 +01:00
antirez d526d09e41 Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-01-20 16:50:02 +01:00
antirez 9738e587da Fixed compilation on FreeBSD 2011-01-20 10:59:25 +01:00
antirez da67b5f59a typo fixed in release notes for 2.2.0 RC3 2011-01-17 12:21:59 +01:00
antirez 825d2904d1 release notes updated for 2.2.0 RC3 2011-01-17 12:19:15 +01:00
Pieter Noordhuis 3becef9ee6 Reverse commits changing sorted set code for 2.2 2011-01-17 11:15:50 +01:00
antirez 2ccf6e81a6 version bumped to 2.1.11 2011-01-17 10:12:57 +01:00
Pieter Noordhuis e18b59ae7e Remove client from list of unblocked clients when it is free'd 2011-01-17 10:03:21 +01:00
Pieter Noordhuis 8ff1353766 Merge branch '2.2' of git://github.com/antirez/redis into 2.2 2011-01-17 09:44:12 +01:00
antirez 99677e89d0 default hash encoding settings also fixed in redis.conf 2011-01-15 22:32:34 +01:00
antirez f43502fb0e lame typo causing bad hashes memory characteristic 2011-01-15 22:24:48 +01:00
antirez ebb07fb4bb implemented two new INFO fields showing the size of clients max input and output buffers. 2011-01-14 10:20:35 +01:00
antirez c13c4080e5 useless expression removed in zmalloc.c 2011-01-14 09:57:07 +01:00
antirez 360664c9c8 master-slave replication fixed, it was not listing any key using KEYS command in the slave. 2011-01-14 09:54:41 +01:00
Pieter Noordhuis 7ac1b364fe Merge branch 'zset-range' into 2.2 2011-01-13 16:30:02 +01:00
Pieter Noordhuis 1b2b8cbbde Compiler should decide on inlining 2011-01-13 16:06:03 +01:00
antirez f487bbbfe2 allocation stats in INFO 2011-01-09 15:57:46 +01:00
Pieter Noordhuis 4b2499cf2f Remove newlines for networking errors 2011-01-05 10:50:47 +01:00
Pieter Noordhuis 01a0b09886 Remove glueoutputbuf from redis.conf 2011-01-05 10:44:54 +01:00
Pieter Noordhuis 90419b5681 Remove glueoutputbuf option and broken code 2011-01-05 10:37:23 +01:00
antirez ae7db3fe83 Merge remote branch 'pietern/bench-fix' into 2.2 2010-12-23 13:17:54 +01:00
Pieter Noordhuis 454f4bfd28 Fix compiler warnings on Solaris 2010-12-23 13:02:22 +01:00
Pieter Noordhuis 1408d147e4 limits.h is already included from redis.h 2010-12-23 13:02:22 +01:00
Pieter Noordhuis 78055bd4ca Solaris 10 doesn't know AF_LOCAL 2010-12-23 13:02:22 +01:00
Pieter Noordhuis 736d447380 Solaris doesn't support -rdynamic 2010-12-23 13:02:22 +01:00
Pieter Noordhuis 71b6f64f2a Update hiredis to 0.9.2 2010-12-23 13:02:21 +01:00
Pieter Noordhuis 2380388974 Randomize keys and set start time when first write event fires 2010-12-23 11:22:40 +01:00
Pieter Noordhuis 3c49070b35 Find substrings to randomize when the client is created 2010-12-23 11:04:44 +01:00
Pieter Noordhuis d69a483556 Make the MSET benchmark *really* work with 10 keys 2010-12-22 18:39:52 +01:00
Pieter Noordhuis f2f2424e00 Remove code duplication 2010-12-22 18:31:33 +01:00
antirez c4e93cadfb Merge branch '2.2' of github.com:antirez/redis into 2.2 2010-12-20 13:35:13 +01:00
antirez 0540df22ba version bumped to 2.1.10 2010-12-20 13:34:07 +01:00
antirez 9ac5be2ebe Improved release notes. Added RC2 changes 2010-12-20 13:32:54 +01:00
antirez b902c1413e TODO file just pointing to the master branch TODO file 2010-12-20 13:16:38 +01:00
antirez 54e9f28922 Merge branch '2.2' of github.com:antirez/redis into 2.2 2010-12-20 13:14:36 +01:00
antirez dd889d884f removed some dead code, added evicted keys counter separated from expired keys. 2010-12-19 15:15:33 +01:00
antirez 1443a814d0 overflow detection in INCR family functions 2010-12-19 12:23:35 +01:00
Didier SpeziaandPieter Noordhuis f474a5bd4e Add wait states to deal with many connections. 2010-12-18 12:11:26 +01:00
Pieter Noordhuis 1cd3c1e08c Use multi-bulk protocol by default in redis-benchmark 2010-12-18 12:10:24 +01:00
Pieter Noordhuis 174df6fe49 Re-use variable data in redis-benchmark 2010-12-16 23:41:58 +01:00
Pieter Noordhuis 53f1d81712 Fix NULL-termination of variable data in redis-benchmark 2010-12-16 23:35:56 +01:00
Pieter Noordhuis 021321e0ef Update hiredis to 0.9.2 2010-12-16 23:32:02 +01:00
antirez 076ff11c06 fixed typo in release notes 2010-12-15 18:10:15 +01:00
antirez 6418b4c790 help.h updated 2010-12-15 17:39:40 +01:00
antirez 59aee5513d added the mandatory Cheers in the release notes 2010-12-15 17:19:01 +01:00
antirez 9fecc4057a release notes, info on 2.0 compatibility 2010-12-15 17:18:15 +01:00
antirez 51716cbdb8 added more info in the release notes 2010-12-15 17:00:54 +01:00
antirez 9a7a07a466 removed a line from release notes that was not true (feature already backported into 2.0) 2010-12-15 16:54:08 +01:00
antirez 0898aa462e typo fixed 2010-12-15 16:51:01 +01:00
antirez 4c4dec7202 release notes added 2010-12-15 16:44:29 +01:00
antirez 401c3e213c bulk transfers limited to 512 MB as this is the new limit of all the redis strings 2010-12-15 16:07:49 +01:00
antirez beb1aab3fd Merge remote branch 'pietern/cli' 2010-12-15 16:03:43 +01:00
Pieter Noordhuis 28c07c7bf8 Specify multi-bulk delimiter via options 2010-12-15 16:02:07 +01:00
antirez 6674e3b910 added new HTML doc pages 2010-12-15 15:59:45 +01:00
Pieter Noordhuis 65add0a311 Improved raw output mode
Raw output mode is selected by default when STDOUT is not a tty. For
ttys, raw output can be forced using the option "--raw".
2010-12-15 15:59:06 +01:00
antirez b9f7e9e63c HTML doc updated 2010-12-15 15:49:29 +01:00
antirez 2136a880a7 version bumped to 2.1.8 2010-12-15 15:22:04 +01:00
Pieter Noordhuis f18e059e82 Make redis-cli help a little better 2010-12-15 15:00:47 +01:00
Pieter Noordhuis c392edf531 Show redis-cli version with repository information if present 2010-12-15 14:34:05 +01:00
Pieter Noordhuis c8061392e1 Minor changes to Makefile 2010-12-15 12:48:12 +01:00
antirez 53f0efe28c Merge remote branch 'pietern/string-patches' 2010-12-15 11:52:55 +01:00
Pieter Noordhuis 7d5f5712d9 Update tests for STRLEN 2010-12-15 11:49:39 +01:00
Pieter Noordhuis ad1b4f4f59 Use helper function for string object length 2010-12-15 11:49:04 +01:00
Pieter Noordhuis 1333f98dd2 Use helper functions in APPEND 2010-12-15 11:40:36 +01:00
Pieter Noordhuis 8f8eeffec1 Disable negative offsets for SETRANGE 2010-12-15 11:30:50 +01:00
Pieter Noordhuis e983cf34be Add fuzzy test for SETBIT 2010-12-15 11:20:54 +01:00
antirez 180e07b8c0 fixed test rendering when test failed in non verbose mode 2010-12-15 11:04:04 +01:00
antirez 7982173fef print test names inline in the non verbose output mode 2010-12-15 10:44:36 +01:00
antirez eae9cce166 colorized make test output when the verbose output is disabled (default) 2010-12-15 10:14:34 +01:00
Pieter Noordhuis 30407e1f4f Make SETBIT return original bit value 2010-12-15 00:42:32 +01:00
antirez 6a246b1e7e special encoding limits redefined, and all the config options for special encodings added in the example redis.conf file 2010-12-14 18:23:52 +01:00
antirez cc7c4158bc Merge remote branch 'jonahharris/syslog' 2010-12-14 17:53:28 +01:00
antirez 8e33831b2b Merge remote branch 'pietern/cli-monitor' 2010-12-14 17:48:52 +01:00
antirez 648e965460 removed a test that will never be true fixing the compilation on Linux 2010-12-14 17:46:20 +01:00
antirez 603e616bf4 Merge branch 'master' of github.com:antirez/redis 2010-12-14 17:42:46 +01:00
antirez 57997664ea Merge remote branch 'pietern/strrange' 2010-12-14 17:42:01 +01:00
Pieter Noordhuis 2b2eca1f56 Zero-pad timestamps in MONITOR output
Original report and fix:
http://code.google.com/p/redis/issues/detail?id=404
2010-12-14 17:39:34 +01:00
antirez 96b5d05fde initialized a few vars just to avoid warnings, not a real problem. 2010-12-14 16:39:33 +01:00
antirez f858c11d7d Merge remote branch 'pietern/brpoplpush' 2010-12-14 16:26:37 +01:00
Pieter Noordhuis d8f160a848 Add test cases for GETRANGE against integer-encoded strings 2010-12-14 15:35:35 +01:00
Pieter Noordhuis ef11bcccca Refactor and rename SUBSTR to GETRANGE
SUBSTR is renamed to GETRANGE to have better consistency between command
names (with SETRANGE as its dual). GETRANGE is still aliased as SUBSTR.
2010-12-14 15:16:29 +01:00
Pieter Noordhuis 9f9e1ceaa0 Add SETRANGE command implementation and tests 2010-12-14 14:20:51 +01:00
antirez 8c304be359 cow friendly HGETALL and variants 2010-12-14 12:10:51 +01:00
Pieter Noordhuis 7ecd4644e7 Don't decode object on STRLEN when not necessary 2010-12-14 10:42:41 +01:00
antirez 5ce3a24dba Linenoise updated to latest version 2010-12-10 19:22:13 +01:00
antirez afd438dfff previouse INCR implementation restored, was actually faster for some reson not fully clear at the moment 2010-12-10 19:15:00 +01:00
antirez d6107fd6f1 Merge remote branch 'pietern/testverbosity' 2010-12-10 17:24:03 +01:00
antirez a15742a41b dont take the fast path for INCR if the resulting integer will fit into a shared integer range 2010-12-10 17:23:38 +01:00
antirez b215a496a4 faster INCR doing far less allocation in common cases 2010-12-10 17:03:38 +01:00
Pieter Noordhuis 6f8a32d5c7 Be less verbose in testing; improve error handling 2010-12-10 16:13:21 +01:00
antirez dd48de748c fixed compilation on Linux 2010-12-10 15:47:01 +01:00
antirez 4b918769a1 command line option in test suite to activate valgrind mode 2010-12-10 15:40:48 +01:00
antirez e9bc56dbed valgrind suppression file added 2010-12-10 15:36:04 +01:00
antirez 3d24304ff9 HGET HMGET are now COW friendly, plus API refactoring and changes needed for the new implementation. 2010-12-10 15:17:55 +01:00
Pieter Noordhuis cc20906390 Change function name to match what it does 2010-12-10 12:16:16 +01:00
Pieter Noordhuis 586500c0ef Typo 2010-12-10 12:06:24 +01:00
Pieter Noordhuis eae33c1c81 Add generic function to grow an sds value
Move logic concerned with setting a bit in an sds to the SETBIT command
instead of keeping it in sds.c. The function to grow an sds can and will
be reused for a command to set a range within a string value.
2010-12-10 11:58:21 +01:00
antirez 1b508da7ca SINTER/MEMBERS are now COW friendly, also some refactoring around was needed to get this result. 2010-12-09 23:01:09 +01:00
Jonah H. Harris 8b5db0a8dc removed dup syslog-ident conditional 2010-12-09 11:26:33 -05:00
Pieter Noordhuis 076f88d657 Enforce maximum string value length of 512MB 2010-12-09 17:16:10 +01:00
Jonah H. Harris e1a586ee69 syslog support 2010-12-09 11:10:21 -05:00
Pieter Noordhuis 3c1bf4957e Add commands SETBIT/GETBIT 2010-12-09 16:39:33 +01:00
Pieter Noordhuis 4769dc7826 Undo rename of function names where something went wrong 2010-12-09 10:37:35 +01:00
antirez a5be65f71c COW friendly versions of SPOP and SRANDMEMBER commands, with some change to the set encoding-agnostic API. 2010-12-09 10:21:02 +01:00
Pieter Noordhuis 039357e471 Move logic concerned with zset ranges
This also optimizes ZREVRANGEBYSCORE for pathological cases where a
sorted set contains many elements with the same score. Previously,
it would traverse the list from back to front in such a case.
2010-12-07 23:21:07 +01:00
antirez d51ebef509 LRANGE converted into a COW friendly command. Some refactoring, comment, and new addReply*() family function added in the process. 2010-12-07 16:33:13 +01:00
Pieter Noordhuis a4ce758155 Don't execute commands for clients when they are unblocked 2010-12-06 16:39:39 +01:00
Pieter Noordhuis ecf9401415 Fix case and indent 2010-12-06 16:04:42 +01:00
Pieter Noordhuis 8a88c368ed Check other blocked clients when value could not be pushed 2010-12-06 16:04:10 +01:00
Pieter Noordhuis ac06fc011d Move code for pushing on a (blocking) RPOPLPUSH 2010-12-06 14:48:58 +01:00
Pieter Noordhuis 5fa95ad763 Rename blpop_blocked_clients to bpop_blocked_clients 2010-12-06 14:05:01 +01:00
Pieter Noordhuis c8a0070a61 Move timeout logic 2010-12-06 13:45:48 +01:00
Pieter Noordhuis bc8ffafeb9 Merge branch 'master' into brpoplpush 2010-12-06 12:31:56 +01:00
antirez bbac56c2f8 added support for ctrl-l and clear command into redis-cli. To clear the screen is a good idea from time to time :). Also linenoise updated to the current version to support this new feature. 2010-12-01 11:18:59 +01:00
Michel Martens & Damian JanowskiandMichel Martens baa14ef913 Fix BRPOPLPUSH behavior for all use cases. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens 8987bf23bf Adhere to conventions. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens e3c51c4b1b Rename bstate to bpop. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens 59bd44d1c8 Remove warning. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens 7c25a43adc Handle BRPOPLPUSH inside a transaction. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens ba3b474111 Refactor code for BRPOPLPUSH. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens 357a841714 Move to struct. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens b2a7fd0cf7 BRPOPLPUSH. 2010-11-29 23:52:07 -03:00
Damian Janowski & Michel MartensandMichel Martens 8a979f0390 Fix case in RPOPLPUSH. 2010-11-29 23:52:07 -03:00
108 changed files with 4698 additions and 1417 deletions
-1
View File
@@ -1,4 +1,3 @@
00-RELEASENOTES
*.o
*.rdb
*.log
+238
View File
@@ -0,0 +1,238 @@
Redis 2.2 release notes
Migrating from 2.0 to 2.2
=========================
Redis 2.0 is mostly a strict subset of 2.2. Some return value changed in edge
cases, basicaly it is very unlikely that you will experience any problem
upgrading your 2.0 instances to 2.2, as 2.2 can work as a drop in replacement
for 2.0.
---------
CHANGELOG
---------
What's new in Redis 2.2.15
==========================
* [BUGFIX] FLUSHALL was not replicated nor written into the Append Only File.
* [BUGFIX] FLUSHALL now only performs a sync SAVE if there is at least
one save point configured.
What's new in Redis 2.2.14
==========================
* [BUGFIX] Fixed a rare but possible AOF race condition that could result into
duplicated commands inside the AOF.
* [BUGFIX] Don't replicate SAVE.
* LRANGE optimization may drastically improve performances when querying the
final part of a long list.
* redis-cli now implements a --latency mode to monitory Redis delay.
What's new in Redis 2.2.13
==========================
* [BUGFIX] Fixed issue 593 (BRPOPLPUSH related crash).
* [BUGFIX] Fixed an issue with the networking layer that may prevent Redis from sending the whole reply back to client under extreme conditions.
What's new in Redis 2.2.12
==========================
* The Slowlog feature was backported to Redis 2.2.
* A number of fixes related blocking operations on lists when mixed with
AOF and Replication.
* Fixed bad interactions between EXPIRE, EXPIREAT, and in general volatile
keys when AOF is enabled. More details in the Redis Google Group here:
http://groups.google.com/group/redis-db/browse_frm/thread/5a931fefb88b16d5?tvc=1
* no more allocation stats info in INFO.
* colorized make for 2.2 as well.
* Fixed a problem with AOF when it is stopped via CONFIG SET appendonly no.
* Warn the user enabling VM that VM is deprecated and discouraged.
* prepareForShutdown() fixed for correctness.
* Close the listening sockets on exit for faster restarts.
What's new in Redis 2.2.11
==========================
* Solved a never reported but possibly critical bug in the AOF and RDB
persistence, introduced with the new version of the iterator: In very rare
circumstances the AOF (after rerwite) or the rdb file may contain the same
key more than one time.
What's new in Redis 2.2.10
==========================
* Fix for issue 566. It was possible for SETNX to behave in abnormal ways.
* Work with make v3.80
* redis-cli updated to the same version as the 2.4 branch. Now the
reconnection handling is better. You can prefix commands with a number
to execute the command the specified number of times. When using -r
in non interactive mode it is possible to set a delay between commands.
* Better init script.
What's new in Redis 2.2.9
=========================
Redis 2.2.9 is an important bugfix release:
* A Slave used to rewrite the AOF log after a sync with the master, but the
rewrite was performed at the wrong time, causing inconsistencies in the
AOF file generated.
* [LR]LPUSH replication in presence of BRPOPLPUSH was broken. Fixed.
* Memory leak in BRPOPLPUSH fixed.
* Pub/Sub bug resulting in random crashes fixed.
What's new in Redis 2.2.8
=========================
* A new form of dict.c (hash table implementation) iterator that performs less
copy-on-write of pages, introduced in Redis 2.2.7, caused ZINTERSTORE,
ZUNIONSTORE, SINTER, SINTERSTORE commands to behave in the wrong way.
This bug is now fixed.
* Print version info before running the test with 'make test'. This is mainly
useful for the Continuous Integration system we run.
* Fix for DEBUG DIGEST, key may expire on lookup, producing the wrong result.
* Replication with expire test modified to produce no or less false failures.
* Fixed Z*STORE when dealing with intsets, regression test added.
What's new in Redis 2.2.7
=========================
* Fixed bug #543-2 (the issue was reopened with a completely different report)
that caused Redis to randomly crash on list push performed against lists
with other clients blocked with BLPOP (or variants).
What's new in Redis 2.2.6
=========================
* Fixed bug #543. If you saw Redis instances crashing on List operations
(only happening with a non-default max entry size ziplist setting in
redis.conf) it was almost certainly this problem.
* Fixed a bug with replication where SLAVEOF NO ONE caused a slave to close the
connection with all its slaves.
What's new in Redis 2.2.5
=========================
* Fixed a crash occurring when loading an AOF containing the SPOP command.
What's new in Redis 2.2.4
=========================
* Return value of OBJECT DEBUG against sorted sets fixed, now is "skiplist".
What's new in Redis 2.2.3
=========================
* Fixed issue #503. MONITOR + QUIT (and other combinations) could crash
the server.
* OBJECT command implemented. See http://redis.io/commands/object
* Fixed a problem in redis-cli related to escapes in the form "\x..".
* Fixed a minor memory leak in redis-cli
* Saved RDB on SIGTERM on archs where it was not working properly.
What's new in Redis 2.2.2
=========================
Redis 2.2.2 is a bugfix release. Changelog:
* AOF file descriptor leak after the first rewrite fixed.
* Return value of GETRANGE / SUBSTR is now an empty string if the interval
specified is not valid, or the key does not exist. Was a null bulk reply
before, but this is against the Redis way.
What's new in Redis 2.2.1
=========================
Redis 2.2.1 is a bugfix release. Changelog:
* Fixed an SPOP crash. When using SPOP in a MULTI/EXEC block there was a problem
introduced in the latest release when fixing an SPOP replication/AOF related
bug.
What's new in Redis 2.2.0 final
===============================
Redis 2.2.0 final is both a bug fix and minor enhancement release:
* SPOP is now replicated correctly in AOF and across slaves.
* CONFIG SET/GET for all the special encoding parameters of sets, lists, hashes.
* Now BRPOPLPUSH will reply with single null bulk on timeout.
* Specifying port 0 in redis.conf will tell Redis to don't listen on TCP socket.
* Propagate key eviction to slaves and AOF, similarly to expires.
What's new in Redis 2.1.10 (2.2 Release Candidate 4)
====================================================
Redis 2.2 RC4 is a bug fix and minor enhancement release:
* Fixed timeout error in replication where master took a big time to BGSAVE.
* Introduced explicit PING between master and slave, to reliably detect when
the link is down, even if the socket remains apparently connected.
* Fixed compilation on FreeBSD.
* Removed a small portability issue in redis-benchmark.
What's new in Redis 2.1.10 (2.2 Release Candidate 3)
====================================================
Redis 2.2 RC3 is a bug fix and minor enhancements release:
* Solaris fixes
* Fixes and improvements for redis-benchmark
* New INFO field with memory allocation details
* New INFO fields with info about clients max input/output buffer
* Replication: KEYS fixed in slaves
* Different default thresholds for Hash type memory saving encodings
What's new in Redis 2.1.10 (2.2 Release Candidate 2)
====================================================
Redis 2.2 RC2 is exactly like RC1 with the following minor changes:
* Added evicted keys counter separated from expired keys.
* Overflow detection in INCR family functions.
Enjoy,
Salvatore
What's new in Redis 2.1.9 (2.2 Release Candidate 1)
===================================================
This is the first Release Candidate of Redis 2.2, in our experience the
server is very stable, but in the latest weeks we rewrote part of the internals
in order to use a lot less memory while the saving child process is performing
a BGREWRITEAOF or a BGSAVE, so handle with care for a couple of weeks.
Oh, and I've some very good news: the majority of apps can work if you simply replace 2.2 in your old 2.0 environment. I can't think of any breakage.
WHAT'S NEW IN REDIS 2.2 compared to the 2.0 version?
====================================================
* Specially encoded data types, small lists and sets can now use up to an order of magnitude less memory.
* VM partial rewrite for code cleaness and memory usage.
* Change to the implementation of the top level dictionary for better memory efficienty.
* redis-cli is hugely improved: tab completion, inline help (Thanks to TJ Holowaychuk), raw output, rewritten using the new hiredis C library.
* Networking internals rewritten for efficiency. You can expect LRANGE and similar commands to be at least 10 times faster.
* Most read only commands are now copy-on-write friendly, this means that Redis will use little memory when a saving child is active and the parent process is mostly stressed by read queries.
* Non blocking replication even from the point of view of the slave, with configurable behavior about what to do when the link is disconnected. You can select if serving old data or replying with an error.
* Check-and-set (CAS) transactions with the new WATCH command.
* Now write operations work against keys with an EXPIRE set! Imagine the possibilities.
* New maxmemory eviction policies. It is possible to select among LRU, farest TTL expire, and other algorithms, and if when the memory limit is reached only keys with an expire set or all the keys should be expired.
* SETBIT / GETBIT / SETRANGE / GETRANGE / STRLEN. Now your strings are your arrays!
* Syslog support (Thanks to Jonah H. Harris)
* Unix domain socket support.
* New List related functions LINSERT, LPUSHX, RPUSHX (Thanks to Robey Pointer)
* BRPOPLPUSH (Thanks to Michel Martens and Damian Janowski)
* Much more interesting informations in the INFO output.
* Sorted sets are now less memory hungry.
* Non blocking loading of .rdb / AOF file on startup, with progress information in the INFO output.
* Now Redis has a clean, powerful, supported C library: hiredis.
* Code layout completely new, the 2.0.x huge redis.c file is now splitted in many parts.
* Redis-benchmark rewritten to be faster and in order to use hiredis as well.
* Ability to rename or disable commands from the config file.
* Endless other CPU optimizations and bugs fixed.
Credits: Where not specified the implementation and design are done by Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all this possible. Also many thanks to all the other contributors and the amazing community we have.
Cheers,
Salvatore
+3 -6
View File
@@ -2,10 +2,7 @@ Where to find complete Redis documentation?
-------------------------------------------
This README is just a fast "quick start" document. You can find more detailed
documentation here:
1) http://code.google.com/p/redis
2) Check the 'doc' directory. doc/README.html is a good starting point :)
documentation at http://redis.io
Building Redis
--------------
@@ -23,7 +20,7 @@ You can run a 32 bit Redis binary using:
% make 32bit
After you build Redis is a good idea to test it, using:
After building Redis is a good idea to test it, using:
% make test
@@ -80,7 +77,7 @@ then in another terminal try the following:
You can find the list of all the available commands here:
http://code.google.com/p/redis/wiki/CommandReference
http://redis.io/commands
Enjoy!
+3 -65
View File
@@ -1,66 +1,4 @@
Redis TODO and Roadmap
----------------------
This is a stable release! No TODO file here.
Please check the TODO file in the master branch on github.
VERSION 2.2 TODO (Optimizations and latency)
============================================
* Support for syslog(3).
* Change the implementation of ZCOUNT to use the augmented skiplist in order to be much faster.
* Add an explicit test for MULTI/EXEC reloaded in the AOF.
* Command table -> hash table, with support for command renaming
VM TODO
=======
* Use multiple open FDs against the VM file, one for thread.
* Check what happens performance-wise if instead of creating threads again and again the same threads are reused forever. Note: this requires a way to disable this clients in the child, but waiting for empty new jobs queue can be enough.
STRING COMMANDS
===============
* Implement STRLEN, PEEK, POKE, SETBIT, GETBIT
OTHER IMPORTANT THINGS THAT WILL BE ADDED BUT I'M NOT SURE WHEN
===============================================================
BIG ONES:
* BRPOPLPUSH
* Specially encoded memory-saving integer sets.
* A command to export a JSON dump (there should be mostly working patch needing major reworking).
* Specially encoded sets of integers (this includes a big refactoring providing an higher level layer for Sets manipulation)
SMALL ONES:
* If sizeof(double) == sizeof(void*) we could store the double value of sorted sets directly in place of the pointer instead of allocating it in the heap.
* Delete on writes against expire policy should only happen after argument parsing for commands doing their own arg parsing stuff.
* Give errors when incrementing a key that does not look like an integer, when providing as a sorted set score something can't be parsed as a double, and so forth.
* MSADD (n keys) (n values). See this thread in the Redis google group: http://groups.google.com/group/redis-db/browse_thread/thread/e766d84eb375cd41
* Don't save empty lists / sets / zsets on disk with snapshotting.
* Remove keys when a list / set / zset reaches length of 0.
* An option to exec a command slave-side if the master connection is lost: even cooler: if the script returns "0" the slave elects itself as master, otherwise continue trying to reconnect.
* PING the master from time to time to check if it's gone.
THE "MAYBE" TODO LIST: things that may or may not get implemented
=================================================================
Most of this can be seen just as proposals, the fact they are in this list
it's not a guarantee they'll ever get implemented ;)
* SORT: Don't copy the list into a vector when BY argument is constant.
* Write the hash table size of every db in the dump, so that Redis can resize the hash table just one time when loading a big DB.
* Byte Array type (BA prefixed commands): BASETBIT BAGETBIT BASETU8 U16 U32 U64 S8 S16 S32 S64, ability to atomically INCRBY all the base types. BARANGE to get a range of bytes as a bulk value, BASETRANGE to set a range of bytes.
* Read-only mode.
* Kill the delete-on-write behavior of expires, replicating DELs
* Multiple BY in SORT.
KNOWN BUGS
==========
* LRANGE and other commands are using 32 bit integers for ranges, and overflows are not detected. So LRANGE mylist 0 23498204823094823904823904 will have random effects.
REDIS CLI TODO
==============
* Computer parsable output generation
* Memoize return values so that they can be used later as arguments, like $1
https://github.com/antirez/redis/raw/master/TODO
+1 -1
View File
@@ -1,6 +1,6 @@
An updated list of client libraries for Redis can be found here:
http://code.google.com/p/redis
http://redis.io/clients
All the links are in the front page.
+32 -19
View File
@@ -6,32 +6,38 @@ OBJ = net.o hiredis.o sds.o async.o
BINS = hiredis-example hiredis-test
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
OPTIMIZATION?=-O2
OPTIMIZATION?=-O3
ifeq ($(uname_S),SunOS)
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF)
CCLINK?=-ldl -lnsl -lsocket -lm -lpthread
LDFLAGS?=-L. -Wl,-R,.
DYLIBNAME?=libhiredis.so
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ}
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
else ifeq ($(uname_S),Darwin)
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
CCLINK?= -lm -pthread
OBJARCH?= -arch i386 -arch x86_64
else
ifeq ($(uname_S),Darwin)
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
CCLINK?=-lm -pthread
LDFLAGS?=-L. -Wl,-rpath,.
OBJARCH?=-arch i386 -arch x86_64
DYLIBNAME?=libhiredis.dylib
DYLIB_MAKE_CMD?=libtool -dynamic -o ${DYLIBNAME} -lm ${DEBUG} - ${OBJ}
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=libtool -static -o ${STLIBNAME} - ${OBJ}
else
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
CCLINK?= -lm -pthread
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
CCLINK?=-lm -pthread
LDFLAGS?=-L. -Wl,-rpath,.
DYLIBNAME?=libhiredis.so
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
endif
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -ggdb
endif
CCOPT= $(CFLAGS) $(CCLINK)
DEBUG?= -g -ggdb
PREFIX?= /usr/local
INSTALL_INC= $(PREFIX)/include/hiredis
@@ -43,8 +49,6 @@ all: ${DYLIBNAME} ${BINS}
# Deps (use make dep to generate this)
net.o: net.c fmacros.h net.h
async.o: async.c async.h hiredis.h sds.h util.h
example-libev.o: example-libev.c hiredis.h async.h adapters/libev.h
example-libevent.o: example-libevent.c hiredis.h async.h adapters/libevent.h
example.o: example.c hiredis.h
hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
sds.o: sds.c sds.h
@@ -60,14 +64,23 @@ dynamic: ${DYLIBNAME}
static: ${STLIBNAME}
# Binaries:
hiredis-example-libevent: example-libevent.o ${DYLIBNAME}
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME}
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -levent example-libevent.c
hiredis-example-libev: example-libev.o ${DYLIBNAME}
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME}
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -lev example-libev.c
ifndef AE_DIR
hiredis-example-ae:
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
@false
else
hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME}
$(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) -lhiredis example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
endif
hiredis-%: %.o ${DYLIBNAME}
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -Wl,-rpath,. $<
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis $<
test: hiredis-test
./hiredis-test
+45 -18
View File
@@ -35,16 +35,18 @@ To consume the synchronous API, there are only a few function calls that need to
### Connecting
The function `redisConnect` is used to create a so-called `redisContext`. The context is where
Hiredis holds state for a connection. The `redisContext` struct has an `error` field that is
non-NULL when the connection is in an error state. It contains a string with a textual
representation of the error. After trying to connect to Redis using `redisConnect` you should
check the `error` field to see if establishing the connection was successful:
The function `redisConnect` is used to create a so-called `redisContext`. The
context is where Hiredis holds state for a connection. The `redisContext`
struct has an integer `err` field that is non-zero when an the connection is in
an error state. The field `errstr` will contain a string with a description of
the error. More information on errors can be found in the **Errors** section.
After trying to connect to Redis using `redisConnect` you should
check the `err` field to see if establishing the connection was successful:
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c->error != NULL) {
printf("Error: %s\n", c->error);
// handle error
if (c->err) {
printf("Error: %s\n", c->errstr);
// handle error
}
### Sending commands
@@ -76,8 +78,8 @@ anywhere in an argument:
### Using replies
The return value of `redisCommand` holds a reply when the command was
successfully executed. When the return value is `NULL`, the `error` field
in the context can be used to find out what was the cause of failure.
successfully executed. When an error occurs, the return value is `NULL` and
the `err` field in the context will be set (see section on **Errors**).
Once an error is returned the context cannot be reused and you should set up
a new connection.
@@ -166,7 +168,7 @@ to the `redisCommand` family, apart from not returning a reply:
After calling either function one or more times, `redisGetReply` can be used to receive the
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
the latter means an error occurred while reading a reply. Just as with the other commands,
the `error` field in the context can be used to find out what the cause of this error is.
the `err` field in the context can be used to find out what the cause of this error is.
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
a single call to `write(2)`):
@@ -184,10 +186,35 @@ This API can also be used to implement a blocking subscriber:
reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK) {
// consume message
freeReplyObject(reply);
// consume message
freeReplyObject(reply);
}
### Errors
When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
returned. The `err` field inside the context will be non-zero and set to one of the
following constants:
* **`REDIS_ERR_IO`**:
There was an I/O error while creating the connection, trying to write
to the socket or read from the socket. If you included `errno.h` in your
application, you can use the global `errno` variable to find out what is
wrong.
* **`REDIS_ERR_EOF`**:
The server closed the connection which resulted in an empty read.
* **`REDIS_ERR_PROTOCOL`**:
There was an error while parsing the protocol.
* **`REDIS_ERR_OTHER`**:
Any other error. Currently, it is only used when a specified hostname to connect
to cannot be resolved.
In every case, the `errstr` field in the context will be set to hold a string representation
of the error.
## Asynchronous API
Hiredis comes with an asynchronous API that works easily with any event library.
@@ -197,15 +224,15 @@ and [libevent](http://monkey.org/~provos/libevent/).
### Connecting
The function `redisAsyncConnect` can be used to establish a non-blocking connection to
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `error` field
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
should be checked after creation to see if there were errors creating the connection.
Because the connection that will be created is non-blocking, the kernel is not able to
instantly return if the specified host and port is able to accept a connection.
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->error != NULL) {
printf("Error: %s\n", c->error);
// handle error
if (c->err) {
printf("Error: %s\n", c->errstr);
// handle error
}
The asynchronous context can hold a disconnect callback function that is called when the
@@ -215,7 +242,7 @@ have the following prototype:
void(const redisAsyncContext *c, int status);
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `error`
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
field in the context can be accessed to find out the cause of the error.
The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
+95
View File
@@ -0,0 +1,95 @@
#include <sys/types.h>
#include <ae.h>
#include "../hiredis.h"
#include "../async.h"
typedef struct redisAeEvents {
redisAsyncContext *context;
aeEventLoop *loop;
int fd;
int reading, writing;
} redisAeEvents;
void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
((void)el); ((void)fd); ((void)mask);
redisAeEvents *e = (redisAeEvents*)privdata;
redisAsyncHandleRead(e->context);
}
void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
((void)el); ((void)fd); ((void)mask);
redisAeEvents *e = (redisAeEvents*)privdata;
redisAsyncHandleWrite(e->context);
}
void redisAeAddRead(void *privdata) {
redisAeEvents *e = (redisAeEvents*)privdata;
aeEventLoop *loop = e->loop;
if (!e->reading) {
e->reading = 1;
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
}
}
void redisAeDelRead(void *privdata) {
redisAeEvents *e = (redisAeEvents*)privdata;
aeEventLoop *loop = e->loop;
if (e->reading) {
e->reading = 0;
aeDeleteFileEvent(loop,e->fd,AE_READABLE);
}
}
void redisAeAddWrite(void *privdata) {
redisAeEvents *e = (redisAeEvents*)privdata;
aeEventLoop *loop = e->loop;
if (!e->writing) {
e->writing = 1;
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
}
}
void redisAeDelWrite(void *privdata) {
redisAeEvents *e = (redisAeEvents*)privdata;
aeEventLoop *loop = e->loop;
if (e->writing) {
e->writing = 0;
aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
}
}
void redisAeCleanup(void *privdata) {
redisAeEvents *e = (redisAeEvents*)privdata;
redisAeDelRead(privdata);
redisAeDelWrite(privdata);
free(e);
}
int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisAeEvents *e;
/* Nothing should be attached when something is already attached */
if (ac->_adapter_data != NULL)
return REDIS_ERR;
/* Create container for context and r/w events */
e = (redisAeEvents*)malloc(sizeof(*e));
e->context = ac;
e->loop = loop;
e->fd = c->fd;
e->reading = e->writing = 0;
/* Register functions to start/stop listening for events */
ac->evAddRead = redisAeAddRead;
ac->evDelRead = redisAeDelRead;
ac->evAddWrite = redisAeAddWrite;
ac->evDelWrite = redisAeDelWrite;
ac->evCleanup = redisAeCleanup;
ac->_adapter_data = e;
return REDIS_OK;
}
+40 -19
View File
@@ -10,69 +10,89 @@ typedef struct redisLibevEvents {
ev_io rev, wev;
} redisLibevEvents;
void redisLibevReadEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
((void)loop); ((void)revents);
redisLibevEvents *e = watcher->data;
void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
#if EV_MULTIPLICITY
((void)loop);
#endif
((void)revents);
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
redisAsyncHandleRead(e->context);
}
void redisLibevWriteEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
((void)loop); ((void)revents);
redisLibevEvents *e = watcher->data;
void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
#if EV_MULTIPLICITY
((void)loop);
#endif
((void)revents);
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
redisAsyncHandleWrite(e->context);
}
void redisLibevAddRead(void *privdata) {
redisLibevEvents *e = privdata;
redisLibevEvents *e = (redisLibevEvents*)privdata;
struct ev_loop *loop = e->loop;
((void)loop);
if (!e->reading) {
e->reading = 1;
ev_io_start(e->loop,&e->rev);
ev_io_start(EV_A_ &e->rev);
}
}
void redisLibevDelRead(void *privdata) {
redisLibevEvents *e = privdata;
redisLibevEvents *e = (redisLibevEvents*)privdata;
struct ev_loop *loop = e->loop;
((void)loop);
if (e->reading) {
e->reading = 0;
ev_io_stop(e->loop,&e->rev);
ev_io_stop(EV_A_ &e->rev);
}
}
void redisLibevAddWrite(void *privdata) {
redisLibevEvents *e = privdata;
redisLibevEvents *e = (redisLibevEvents*)privdata;
struct ev_loop *loop = e->loop;
((void)loop);
if (!e->writing) {
e->writing = 1;
ev_io_start(e->loop,&e->wev);
ev_io_start(EV_A_ &e->wev);
}
}
void redisLibevDelWrite(void *privdata) {
redisLibevEvents *e = privdata;
redisLibevEvents *e = (redisLibevEvents*)privdata;
struct ev_loop *loop = e->loop;
((void)loop);
if (e->writing) {
e->writing = 0;
ev_io_stop(e->loop,&e->wev);
ev_io_stop(EV_A_ &e->wev);
}
}
void redisLibevCleanup(void *privdata) {
redisLibevEvents *e = privdata;
redisLibevEvents *e = (redisLibevEvents*)privdata;
redisLibevDelRead(privdata);
redisLibevDelWrite(privdata);
free(e);
}
int redisLibevAttach(redisAsyncContext *ac, struct ev_loop *loop) {
int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisLibevEvents *e;
/* Nothing should be attached when something is already attached */
if (ac->data != NULL)
if (ac->_adapter_data != NULL)
return REDIS_ERR;
/* Create container for context and r/w events */
e = malloc(sizeof(*e));
e = (redisLibevEvents*)malloc(sizeof(*e));
e->context = ac;
#if EV_MULTIPLICITY
e->loop = loop;
#else
e->loop = NULL;
#endif
e->reading = e->writing = 0;
e->rev.data = e;
e->wev.data = e;
@@ -83,10 +103,11 @@ int redisLibevAttach(redisAsyncContext *ac, struct ev_loop *loop) {
ac->evAddWrite = redisLibevAddWrite;
ac->evDelWrite = redisLibevDelWrite;
ac->evCleanup = redisLibevCleanup;
ac->data = e;
ac->_adapter_data = e;
/* Initialize read/write events */
ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
return REDIS_OK;
}
+10 -10
View File
@@ -10,38 +10,38 @@ typedef struct redisLibeventEvents {
void redisLibeventReadEvent(int fd, short event, void *arg) {
((void)fd); ((void)event);
redisLibeventEvents *e = arg;
redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleRead(e->context);
}
void redisLibeventWriteEvent(int fd, short event, void *arg) {
((void)fd); ((void)event);
redisLibeventEvents *e = arg;
redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleWrite(e->context);
}
void redisLibeventAddRead(void *privdata) {
redisLibeventEvents *e = privdata;
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_add(&e->rev,NULL);
}
void redisLibeventDelRead(void *privdata) {
redisLibeventEvents *e = privdata;
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(&e->rev);
}
void redisLibeventAddWrite(void *privdata) {
redisLibeventEvents *e = privdata;
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_add(&e->wev,NULL);
}
void redisLibeventDelWrite(void *privdata) {
redisLibeventEvents *e = privdata;
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(&e->wev);
}
void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = privdata;
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(&e->rev);
event_del(&e->wev);
free(e);
@@ -52,11 +52,11 @@ int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
redisLibeventEvents *e;
/* Nothing should be attached when something is already attached */
if (ac->data != NULL)
if (ac->_adapter_data != NULL)
return REDIS_ERR;
/* Create container for context and r/w events */
e = malloc(sizeof(*e));
e = (redisLibeventEvents*)malloc(sizeof(*e));
e->context = ac;
/* Register functions to start/stop listening for events */
@@ -65,7 +65,7 @@ int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
ac->evAddWrite = redisLibeventAddWrite;
ac->evDelWrite = redisLibeventDelWrite;
ac->evCleanup = redisLibeventCleanup;
ac->data = e;
ac->_adapter_data = e;
/* Initialize and install read/write events */
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
+46 -9
View File
@@ -1,5 +1,7 @@
/*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -38,8 +40,29 @@ void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
/* Set all bytes in the async part of the context to 0 */
memset(ac+sizeof(redisContext),0,sizeof(redisAsyncContext)-sizeof(redisContext));
c = &(ac->c);
/* The regular connect functions will always set the flag REDIS_CONNECTED.
* For the async API, we want to wait until the first write event is
* received up before setting this flag, so reset it here. */
c->flags &= ~REDIS_CONNECTED;
ac->err = 0;
ac->errstr = NULL;
ac->data = NULL;
ac->_adapter_data = NULL;
ac->evAddRead = NULL;
ac->evDelRead = NULL;
ac->evAddWrite = NULL;
ac->evDelWrite = NULL;
ac->evCleanup = NULL;
ac->onConnect = NULL;
ac->onDisconnect = NULL;
ac->replies.head = NULL;
ac->replies.tail = NULL;
return ac;
}
@@ -70,6 +93,14 @@ int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFun
return redisSetReplyObjectFunctions(c,fn);
}
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
if (ac->onConnect == NULL) {
ac->onConnect = fn;
return REDIS_OK;
}
return REDIS_ERR;
}
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
if (ac->onDisconnect == NULL) {
ac->onDisconnect = fn;
@@ -153,7 +184,7 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
}
/* Signal event lib to clean up */
if (ac->evCleanup) ac->evCleanup(ac->data);
if (ac->evCleanup) ac->evCleanup(ac->_adapter_data);
/* Execute callback with proper status */
if (ac->onDisconnect) ac->onDisconnect(ac,status);
@@ -206,7 +237,7 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac);
} else {
/* Always re-schedule reads */
if (ac->evAddRead) ac->evAddRead(ac->data);
if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
redisProcessCallbacks(ac);
}
}
@@ -220,13 +251,19 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
} else {
/* Continue writing when not done, stop writing otherwise */
if (!done) {
if (ac->evAddWrite) ac->evAddWrite(ac->data);
if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
} else {
if (ac->evDelWrite) ac->evDelWrite(ac->data);
if (ac->evDelWrite) ac->evDelWrite(ac->_adapter_data);
}
/* Always schedule reads when something was written */
if (ac->evAddRead) ac->evAddRead(ac->data);
/* Always schedule reads after writes */
if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
/* Fire onConnect when this is the first write event. */
if (!(c->flags & REDIS_CONNECTED)) {
c->flags |= REDIS_CONNECTED;
if (ac->onConnect) ac->onConnect(ac);
}
}
}
@@ -249,7 +286,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
__redisPushCallback(&ac->replies,&cb);
/* Always schedule a write when the write buffer is non-empty */
if (ac->evAddWrite) ac->evAddWrite(ac->data);
if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
return REDIS_OK;
}
+20 -2
View File
@@ -31,6 +31,10 @@
#define __HIREDIS_ASYNC_H
#include "hiredis.h"
#ifdef __cplusplus
extern "C" {
#endif
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
/* Reply callback prototype and container */
@@ -46,8 +50,9 @@ typedef struct redisCallbackList {
redisCallback *head, *tail;
} redisCallbackList;
/* Disconnect callback prototype */
/* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*);
/* Context for an async connection to Redis */
typedef struct redisAsyncContext {
@@ -58,6 +63,12 @@ typedef struct redisAsyncContext {
int err;
char *errstr;
/* Not used by hiredis */
void *data;
/* Used by the different event lib adapters to store their private data */
void *_adapter_data;
/* Called when the library expects to start reading/writing.
* The supplied functions should be idempotent. */
void (*evAddRead)(void *privdata);
@@ -65,12 +76,14 @@ typedef struct redisAsyncContext {
void (*evAddWrite)(void *privdata);
void (*evDelWrite)(void *privdata);
void (*evCleanup)(void *privdata);
void *data;
/* Called when either the connection is terminated due to an error or per
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
redisDisconnectCallback *onDisconnect;
/* Called when the first write event was received. */
redisConnectCallback *onConnect;
/* Reply callbacks */
redisCallbackList replies;
} redisAsyncContext;
@@ -78,6 +91,7 @@ typedef struct redisAsyncContext {
/* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncDisconnect(redisAsyncContext *ac);
@@ -91,4 +105,8 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
#ifdef __cplusplus
}
#endif
#endif
+53
View File
@@ -0,0 +1,53 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "hiredis.h"
#include "async.h"
#include "adapters/ae.h"
/* Put event loop in the global scope, so it can be explicitly stopped */
static aeEventLoop *loop;
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c) {
((void)c);
printf("connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
}
printf("disconnected...\n");
aeStop(loop);
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
loop = aeCreateEventLoop();
redisAeAttach(loop, c);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
aeMain(loop);
return 0;
}
+9 -3
View File
@@ -15,15 +15,20 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c) {
((void)c);
printf("connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
}
printf("disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct ev_loop *loop = ev_default_loop(0);
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
@@ -32,10 +37,11 @@ int main (int argc, char **argv) {
return 1;
}
redisLibevAttach(c,loop);
redisLibevAttach(EV_DEFAULT_ c);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
ev_loop(loop, 0);
ev_loop(EV_DEFAULT_ 0);
return 0;
}
+7
View File
@@ -15,10 +15,16 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c) {
((void)c);
printf("connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
}
printf("disconnected...\n");
}
int main (int argc, char **argv) {
@@ -33,6 +39,7 @@ int main (int argc, char **argv) {
}
redisLibeventAttach(c,base);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+1 -1
View File
@@ -17,7 +17,7 @@ int main(void) {
/* PING server */
reply = redisCommand(c,"PING");
printf("PONG: %s\n", reply->str);
printf("PING: %s\n", reply->str);
freeReplyObject(reply);
/* Set a key */
+187 -56
View File
@@ -1,5 +1,7 @@
/*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -32,6 +34,7 @@
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include "hiredis.h"
#include "net.h"
@@ -44,10 +47,12 @@ typedef struct redisReader {
void *reply; /* holds temporary reply */
sds buf; /* read buffer */
unsigned int pos; /* buffer cursor */
size_t pos; /* buffer cursor */
size_t len; /* buffer length */
redisReadTask rstack[3]; /* stack of read tasks */
redisReadTask rstack[9]; /* stack of read tasks */
int ridx; /* index of stack */
void *privdata; /* user-settable arbitrary field */
} redisReader;
static redisReply *createReplyObject(int type);
@@ -68,7 +73,7 @@ static redisReplyObjectFunctions defaultFunctions = {
/* Create a reply object */
static redisReply *createReplyObject(int type) {
redisReply *r = calloc(sizeof(*r),1);
redisReply *r = malloc(sizeof(*r));
if (!r) redisOOM();
r->type = type;
@@ -88,9 +93,10 @@ void freeReplyObject(void *reply) {
if (r->element[j]) freeReplyObject(r->element[j]);
free(r->element);
break;
default:
if (r->str != NULL)
free(r->str);
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
free(r->str);
break;
}
free(r);
@@ -111,7 +117,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
r->len = len;
if (task->parent) {
redisReply *parent = task->parent;
redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
@@ -124,7 +130,7 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL)
redisOOM();
if (task->parent) {
redisReply *parent = task->parent;
redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
@@ -135,7 +141,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
redisReply *r = createReplyObject(REDIS_REPLY_INTEGER);
r->integer = value;
if (task->parent) {
redisReply *parent = task->parent;
redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
@@ -145,7 +151,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
static void *createNilObject(const redisReadTask *task) {
redisReply *r = createReplyObject(REDIS_REPLY_NIL);
if (task->parent) {
redisReply *parent = task->parent;
redisReply *parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY);
parent->element[task->idx] = r;
}
@@ -154,7 +160,7 @@ static void *createNilObject(const redisReadTask *task) {
static char *readBytes(redisReader *r, unsigned int bytes) {
char *p;
if (sdslen(r->buf)-r->pos >= bytes) {
if (r->len-r->pos >= bytes) {
p = r->buf+r->pos;
r->pos += bytes;
return p;
@@ -162,20 +168,60 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
return NULL;
}
static char *seekNewline(char *s) {
/* Find pointer to \r\n without strstr */
while (s != NULL) {
s = strchr(s,'\r');
if (s != NULL) {
if (s[1] == '\n')
break;
else
s++;
/* Find pointer to \r\n. */
static char *seekNewline(char *s, size_t len) {
int pos = 0;
int _len = len-1;
/* Position should be < len-1 because the character at "pos" should be
* followed by a \n. Note that strchr cannot be used because it doesn't
* allow to search a limited length and the buffer that is being searched
* might not have a trailing NULL character. */
while (pos < _len) {
while(pos < _len && s[pos] != '\r') pos++;
if (s[pos] != '\r') {
/* Not found. */
return NULL;
} else {
break;
if (s[pos+1] == '\n') {
/* Found. */
return s+pos;
} else {
/* Continue searching. */
pos++;
}
}
}
return s;
return NULL;
}
/* Read a long long value starting at *s, under the assumption that it will be
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
static long long readLongLong(char *s) {
long long v = 0;
int dec, mult = 1;
char c;
if (*s == '-') {
mult = -1;
s++;
} else if (*s == '+') {
mult = 1;
s++;
}
while ((c = *(s++)) != '\r') {
dec = c - '0';
if (dec >= 0 && dec < 10) {
v *= 10;
v += dec;
} else {
/* Should not happen... */
return -1;
}
}
return mult*v;
}
static char *readLine(redisReader *r, int *_len) {
@@ -183,7 +229,7 @@ static char *readLine(redisReader *r, int *_len) {
int len;
p = r->buf+r->pos;
s = seekNewline(p);
s = seekNewline(p,(r->len-r->pos));
if (s != NULL) {
len = s-(r->buf+r->pos);
r->pos += len+2; /* skip \r\n */
@@ -227,7 +273,7 @@ static int processLineItem(redisReader *r) {
if ((p = readLine(r,&len)) != NULL) {
if (r->fn) {
if (cur->type == REDIS_REPLY_INTEGER) {
obj = r->fn->createInteger(cur,strtoll(p,NULL,10));
obj = r->fn->createInteger(cur,readLongLong(p));
} else {
obj = r->fn->createString(cur,p,len);
}
@@ -235,9 +281,8 @@ static int processLineItem(redisReader *r) {
obj = (void*)(size_t)(cur->type);
}
/* If there is no root yet, register this object as root. */
if (r->reply == NULL)
r->reply = obj;
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return 0;
}
@@ -250,32 +295,36 @@ static int processBulkItem(redisReader *r) {
char *p, *s;
long len;
unsigned long bytelen;
int success = 0;
p = r->buf+r->pos;
s = seekNewline(p);
s = seekNewline(p,r->len-r->pos);
if (s != NULL) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
len = strtol(p,NULL,10);
len = readLongLong(p);
if (len < 0) {
/* The nil object can always be created. */
obj = r->fn ? r->fn->createNil(cur) :
(void*)REDIS_REPLY_NIL;
success = 1;
} else {
/* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= sdslen(r->buf)) {
if (r->pos+bytelen <= r->len) {
obj = r->fn ? r->fn->createString(cur,s+2,len) :
(void*)REDIS_REPLY_STRING;
success = 1;
}
}
/* Proceed when obj was created. */
if (obj != NULL) {
if (success) {
r->pos += bytelen;
if (r->reply == NULL)
r->reply = obj;
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return 0;
}
@@ -288,9 +337,19 @@ static int processMultiBulkItem(redisReader *r) {
void *obj;
char *p;
long elements;
int root = 0;
/* Set error for nested multi bulks with depth > 1 */
if (r->ridx == 8) {
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
"No support for nested multi bulk replies with depth > 7"));
return -1;
}
if ((p = readLine(r,NULL)) != NULL) {
elements = strtol(p,NULL,10);
elements = readLongLong(p);
root = (r->ridx == 0);
if (elements == -1) {
obj = r->fn ? r->fn->createNil(cur) :
(void*)REDIS_REPLY_NIL;
@@ -302,19 +361,21 @@ static int processMultiBulkItem(redisReader *r) {
/* Modify task stack when there are more than 0 elements. */
if (elements > 0) {
cur->elements = elements;
cur->obj = obj;
r->ridx++;
r->rstack[r->ridx].type = -1;
r->rstack[r->ridx].elements = -1;
r->rstack[r->ridx].parent = obj;
r->rstack[r->ridx].idx = 0;
r->rstack[r->ridx].obj = NULL;
r->rstack[r->ridx].parent = cur;
r->rstack[r->ridx].privdata = r->privdata;
} else {
moveToNextTask(r);
}
}
/* Object was created, so we can always continue. */
if (r->reply == NULL)
r->reply = obj;
/* Set reply if this is the root object. */
if (root) r->reply = obj;
return 0;
}
return -1;
@@ -347,7 +408,7 @@ static int processItem(redisReader *r) {
default:
byte = sdscatrepr(sdsempty(),p,1);
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
"protocol error, got %s as reply type byte", byte));
"Protocol error, got %s as reply type byte", byte));
sdsfree(byte);
return -1;
}
@@ -368,8 +429,7 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r);
default:
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
"unknown item type '%d'", cur->type));
assert(NULL);
return -1;
}
}
@@ -394,6 +454,17 @@ int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFuncti
return REDIS_ERR;
}
/* Set the private data field that is used in the read tasks. This argument can
* be used to curry arbitrary data to the custom reply object functions. */
int redisReplyReaderSetPrivdata(void *reader, void *privdata) {
redisReader *r = reader;
if (r->reply == NULL) {
r->privdata = privdata;
return REDIS_OK;
}
return REDIS_ERR;
}
/* External libraries wrapping hiredis might need access to the temporary
* variable while the reply is built up. When the reader contains an
* object in between receiving some bytes to parse, this object might
@@ -437,8 +508,10 @@ void redisReplyReaderFeed(void *reader, char *buf, size_t len) {
redisReader *r = reader;
/* Copy the provided buffer. */
if (buf != NULL && len >= 1)
if (buf != NULL && len >= 1) {
r->buf = sdscatlen(r->buf,buf,len);
r->len = sdslen(r->buf);
}
}
int redisReplyReaderGetReply(void *reader, void **reply) {
@@ -446,15 +519,17 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
if (reply != NULL) *reply = NULL;
/* When the buffer is empty, there will never be a reply. */
if (sdslen(r->buf) == 0)
if (r->len == 0)
return REDIS_OK;
/* Set first item to process when the stack is empty. */
if (r->ridx == -1) {
r->rstack[0].type = -1;
r->rstack[0].elements = -1;
r->rstack[0].parent = NULL;
r->rstack[0].idx = -1;
r->rstack[0].obj = NULL;
r->rstack[0].parent = NULL;
r->rstack[0].privdata = r->privdata;
r->ridx = 0;
}
@@ -465,14 +540,15 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
/* Discard the consumed part of the buffer. */
if (r->pos > 0) {
if (r->pos == sdslen(r->buf)) {
if (r->pos == r->len) {
/* sdsrange has a quirck on this edge case. */
sdsfree(r->buf);
r->buf = sdsempty();
} else {
r->buf = sdsrange(r->buf,r->pos,sdslen(r->buf));
r->buf = sdsrange(r->buf,r->pos,r->len);
}
r->pos = 0;
r->len = sdslen(r->buf);
}
/* Emit a reply when there is one. */
@@ -481,7 +557,7 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
r->reply = NULL;
/* Destroy the buffer when it is empty and is quite large. */
if (sdslen(r->buf) == 0 && sdsavail(r->buf) > 16*1024) {
if (r->len == 0 && sdsavail(r->buf) > 16*1024) {
sdsfree(r->buf);
r->buf = sdsempty();
r->pos = 0;
@@ -525,6 +601,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
char *cmd = NULL; /* final command */
int pos; /* position in final command */
sds current; /* current argument */
int interpolated = 0; /* did we do interpolation on an argument? */
char **argv = NULL;
int argc = 0, j;
int totlen = 0;
@@ -541,6 +618,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
if (sdslen(current) != 0) {
addArgument(current, &argv, &argc, &totlen);
current = sdsempty();
interpolated = 0;
}
} else {
current = sdscatlen(current,c,1);
@@ -549,16 +627,74 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
switch(c[1]) {
case 's':
arg = va_arg(ap,char*);
current = sdscat(current,arg);
size = strlen(arg);
if (size > 0)
current = sdscatlen(current,arg,size);
interpolated = 1;
break;
case 'b':
arg = va_arg(ap,char*);
size = va_arg(ap,size_t);
current = sdscatlen(current,arg,size);
if (size > 0)
current = sdscatlen(current,arg,size);
interpolated = 1;
break;
case '%':
cmd = sdscat(cmd,"%");
current = sdscat(current,"%");
break;
default:
/* Try to detect printf format */
{
char _format[16];
const char *_p = c+1;
size_t _l = 0;
va_list _cpy;
/* Flags */
if (*_p != '\0' && *_p == '#') _p++;
if (*_p != '\0' && *_p == '0') _p++;
if (*_p != '\0' && *_p == '-') _p++;
if (*_p != '\0' && *_p == ' ') _p++;
if (*_p != '\0' && *_p == '+') _p++;
/* Field width */
while (*_p != '\0' && isdigit(*_p)) _p++;
/* Precision */
if (*_p == '.') {
_p++;
while (*_p != '\0' && isdigit(*_p)) _p++;
}
/* Modifiers */
if (*_p != '\0') {
if (*_p == 'h' || *_p == 'l') {
/* Allow a single repetition for these modifiers */
if (_p[0] == _p[1]) _p++;
_p++;
}
}
/* Conversion specifier */
if (*_p != '\0' && strchr("diouxXeEfFgGaA",*_p) != NULL) {
_l = (_p+1)-c;
if (_l < sizeof(_format)-2) {
memcpy(_format,c,_l);
_format[_l] = '\0';
va_copy(_cpy,ap);
current = sdscatvprintf(current,_format,_cpy);
interpolated = 1;
va_end(_cpy);
/* Update current position (note: outer blocks
* increment c twice so compensate here) */
c = _p-1;
}
}
/* Consume and discard vararg */
va_arg(ap,void);
}
}
c++;
}
@@ -566,7 +702,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
}
/* Add the last argument if needed */
if (sdslen(current) != 0) {
if (interpolated || sdslen(current) != 0) {
addArgument(current, &argv, &argc, &totlen);
} else {
sdsfree(current);
@@ -664,7 +800,6 @@ void __redisSetError(redisContext *c, int type, const sds errstr) {
static redisContext *redisContextInit() {
redisContext *c = calloc(sizeof(redisContext),1);
c->fd = -1; /* quick fix for a bug that should be addressed differently */
c->err = 0;
c->errstr = NULL;
c->obuf = sdsempty();
@@ -692,7 +827,6 @@ void redisFree(redisContext *c) {
redisContext *redisConnect(const char *ip, int port) {
redisContext *c = redisContextInit();
c->flags |= REDIS_BLOCK;
c->flags |= REDIS_CONNECTED;
redisContextConnectTcp(c,ip,port);
return c;
}
@@ -700,7 +834,6 @@ redisContext *redisConnect(const char *ip, int port) {
redisContext *redisConnectNonBlock(const char *ip, int port) {
redisContext *c = redisContextInit();
c->flags &= ~REDIS_BLOCK;
c->flags |= REDIS_CONNECTED;
redisContextConnectTcp(c,ip,port);
return c;
}
@@ -708,7 +841,6 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
redisContext *redisConnectUnix(const char *path) {
redisContext *c = redisContextInit();
c->flags |= REDIS_BLOCK;
c->flags |= REDIS_CONNECTED;
redisContextConnectUnix(c,path);
return c;
}
@@ -716,7 +848,6 @@ redisContext *redisConnectUnix(const char *path) {
redisContext *redisConnectUnixNonBlock(const char *path) {
redisContext *c = redisContextInit();
c->flags &= ~REDIS_BLOCK;
c->flags |= REDIS_CONNECTED;
redisContextConnectUnix(c,path);
return c;
}
+16 -3
View File
@@ -1,5 +1,7 @@
/*
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -34,7 +36,7 @@
#define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 9
#define HIREDIS_PATCH 0
#define HIREDIS_PATCH 2
#define REDIS_ERR -1
#define REDIS_OK 0
@@ -62,12 +64,16 @@
* should be terminated once all replies have been read. */
#define REDIS_DISCONNECTING 0x4
#define REDIS_REPLY_ERROR 0
#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#ifdef __cplusplus
extern "C" {
#endif
/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
@@ -82,8 +88,10 @@ typedef struct redisReply {
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
void *parent; /* optional pointer to parent object */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
void *privdata; /* user-settable arbitrary field */
} redisReadTask;
typedef struct redisReplyObjectFunctions {
@@ -112,6 +120,7 @@ typedef struct redisContext {
void freeReplyObject(void *reply);
void *redisReplyReaderCreate();
int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn);
int redisReplyReaderSetPrivdata(void *reader, void *privdata);
void *redisReplyReaderGetObject(void *reader);
char *redisReplyReaderGetError(void *reader);
void redisReplyReaderFree(void *ptr);
@@ -154,4 +163,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap);
void *redisCommand(redisContext *c, const char *format, ...);
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
#ifdef __cplusplus
}
#endif
#endif
+6 -3
View File
@@ -1,8 +1,9 @@
/* Extracted from anet.c to work properly with Hiredis error reporting.
*
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
@@ -43,7 +44,7 @@
#include <stdarg.h>
#include <stdio.h>
#include "hiredis.h"
#include "net.h"
#include "sds.h"
/* Forward declaration */
@@ -114,7 +115,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
he = gethostbyname(addr);
if (he == NULL) {
__redisSetError(c,REDIS_ERR_OTHER,
sdscatprintf(sdsempty(),"can't resolve: %s",addr));
sdscatprintf(sdsempty(),"Can't resolve: %s",addr));
close(s);
return REDIS_ERR;
}
@@ -137,6 +138,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
}
c->fd = s;
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
}
@@ -163,5 +165,6 @@ int redisContextConnectUnix(redisContext *c, const char *path) {
}
c->fd = s;
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
}
+6
View File
@@ -31,6 +31,12 @@
#ifndef __NET_H
#define __NET_H
#include "hiredis.h"
#if defined(__sun)
#define AF_LOCAL AF_UNIX
#endif
int redisContextConnectTcp(redisContext *c, const char *addr, int port);
int redisContextConnectUnix(redisContext *c, const char *path);
+110 -32
View File
@@ -47,17 +47,59 @@ static void test_format_commands() {
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
test("Format command with %%s and an empty string: ");
len = redisFormatCommand(&cmd,"SET %s %s","foo","");
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
free(cmd);
test("Format command with %%b string interpolation: ");
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd);
test("Format command with %%b and an empty string: ");
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
free(cmd);
test("Format command with literal %%: ");
len = redisFormatCommand(&cmd,"SET %% %%");
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(1+2)+4+(1+2));
free(cmd);
test("Format command with printf-delegation (long long): ");
len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
len == 4+5+(12+2));
free(cmd);
test("Format command with printf-delegation (float): ");
len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
len == 4+4+(8+2));
free(cmd);
test("Format command with printf-delegation and extra interpolation: ");
len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
len == 4+4+(8+2)+4+(3+2));
free(cmd);
test("Format command with wrong printf format and extra interpolation: ");
len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
len == 4+4+(6+2)+4+(3+2));
free(cmd);
const char *argv[3];
argv[0] = "SET";
argv[1] = "foo";
argv[1] = "foo\0xxx";
argv[2] = "bar";
size_t lens[3] = { 3, 3, 3 };
size_t lens[3] = { 3, 7, 3 };
int argc = 3;
test("Format command by passing argc/argv without lengths: ");
@@ -68,38 +110,29 @@ static void test_format_commands() {
test("Format command by passing argc/argv with lengths: ");
len = redisFormatCommandArgv(&cmd,argc,argv,lens);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
free(cmd);
}
static void test_blocking_connection() {
redisContext *c;
redisReply *reply;
int major, minor;
__connect(&c);
test("Returns I/O error when the connection is lost: ");
reply = redisCommand(c,"QUIT");
test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL);
/* Two conditions may happen, depending on the type of connection.
* When connected via TCP, the socket will not yet be aware of the closed
* connection and the write(2) call will succeed, but the read(2) will
* result in an EOF. When connected via Unix sockets, the socket will be
* immediately aware that it was closed and fail on the write(2) call. */
if (use_unix) {
fprintf(stderr,"Error: %s\n", c->errstr);
assert(c->err == REDIS_ERR_IO &&
strcmp(c->errstr,"Broken pipe") == 0);
} else {
fprintf(stderr,"Error: %s\n", c->errstr);
assert(c->err == REDIS_ERR_EOF &&
strcmp(c->errstr,"Server closed the connection") == 0);
}
freeReplyObject(reply);
test("Returns error when host cannot be resolved: ");
c = redisConnect((char*)"idontexist.local", 6379);
test_cond(c->err == REDIS_ERR_OTHER &&
strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
redisFree(c);
__connect(&c); /* reconnect */
test("Returns error when the port is not open: ");
c = redisConnect((char*)"localhost", 56380);
test_cond(c->err == REDIS_ERR_IO &&
strcmp(c->errstr,"Connection refused") == 0);
redisFree(c);
__connect(&c);
test("Is able to deliver commands: ");
reply = redisCommand(c,"PING");
test_cond(reply->type == REDIS_REPLY_STATUS &&
@@ -112,12 +145,9 @@ static void test_blocking_connection() {
/* Make sure the DB is emtpy */
reply = redisCommand(c,"DBSIZE");
if (reply->type != REDIS_REPLY_INTEGER ||
reply->integer != 0) {
printf("Sorry DB 9 is not empty, test can not continue\n");
if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
printf("Database #9 is not empty, test can not continue\n");
exit(1);
} else {
printf("DB 9 is empty... test can continue\n");
}
freeReplyObject(reply);
@@ -183,6 +213,43 @@ static void test_blocking_connection() {
reply->element[1]->type == REDIS_REPLY_STATUS &&
strcasecmp(reply->element[1]->str,"pong") == 0);
freeReplyObject(reply);
{
/* Find out Redis version to determine the path for the next test */
const char *field = "redis_version:";
char *p, *eptr;
reply = redisCommand(c,"INFO");
p = strstr(reply->str,field);
major = strtol(p+strlen(field),&eptr,10);
p = eptr+1; /* char next to the first "." */
minor = strtol(p,&eptr,10);
freeReplyObject(reply);
}
test("Returns I/O error when the connection is lost: ");
reply = redisCommand(c,"QUIT");
if (major >= 2 && minor > 0) {
/* > 2.0 returns OK on QUIT and read() should be issued once more
* to know the descriptor is at EOF. */
test_cond(strcasecmp(reply->str,"OK") == 0 &&
redisGetReply(c,(void**)&reply) == REDIS_ERR);
freeReplyObject(reply);
} else {
test_cond(reply == NULL);
}
/* On 2.0, QUIT will cause the connection to be closed immediately and
* the read(2) for the reply on QUIT will set the error to EOF.
* On >2.0, QUIT will return with OK and another read(2) needed to be
* issued to find out the socket was closed by the server. In both
* conditions, the error will be set to EOF. */
assert(c->err == REDIS_ERR_EOF &&
strcmp(c->errstr,"Server closed the connection") == 0);
/* Clean up context and reconnect again */
redisFree(c);
__connect(&c);
}
static void test_reply_reader() {
@@ -197,7 +264,7 @@ static void test_reply_reader() {
ret = redisReplyReaderGetReply(reader,NULL);
err = redisReplyReaderGetError(reader);
test_cond(ret == REDIS_ERR &&
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
redisReplyReaderFree(reader);
/* when the reply already contains multiple items, they must be free'd
@@ -210,7 +277,18 @@ static void test_reply_reader() {
ret = redisReplyReaderGetReply(reader,NULL);
err = redisReplyReaderGetError(reader);
test_cond(ret == REDIS_ERR &&
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
redisReplyReaderFree(reader);
test("Set error on nested multi bulks with depth > 1: ");
reader = redisReplyReaderCreate();
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
ret = redisReplyReaderGetReply(reader,NULL);
err = redisReplyReaderGetError(reader);
test_cond(ret == REDIS_ERR &&
strncasecmp(err,"No support for",14) == 0);
redisReplyReaderFree(reader);
test("Works with NULL functions for reply: ");
+35 -2
View File
@@ -9,6 +9,8 @@
* the 2010 UNIX computers around.
*
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -66,6 +68,17 @@
* CUF (CUrsor Forward)
* Sequence: ESC [ n C
* Effect: moves cursor forward of n chars
*
* The following are used to clear the screen: ESC [ H ESC [ 2 J
* This is actually composed of two sequences:
*
* cursorhome
* Sequence: ESC [ H
* Effect: moves the cursor to upper left corner
*
* ED2 (Clear entire screen)
* Sequence: ESC [ 2 J
* Effect: clear the whole screen
*
*/
@@ -265,6 +278,12 @@ static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, si
return c; /* Return last read character */
}
void linenoiseClearScreen(void) {
if (write(STDIN_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
/* nothing to do, just to avoid warning. */
}
}
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
size_t plen = strlen(prompt);
size_t pos = 0;
@@ -301,10 +320,9 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
switch(c) {
case 13: /* enter */
case 4: /* ctrl-d */
history_len--;
free(history[history_len]);
return (len == 0 && c == 4) ? -1 : (int)len;
return (int)len;
case 3: /* ctrl-c */
errno = EAGAIN;
return -1;
@@ -318,6 +336,18 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
refreshLine(fd,prompt,buf,len,pos,cols);
}
break;
case 4: /* ctrl-d, remove char at right of cursor */
if (len > 1 && pos < (len-1)) {
memmove(buf+pos,buf+pos+1,len-pos);
len--;
buf[len] = '\0';
refreshLine(fd,prompt,buf,len,pos,cols);
} else if (len == 0) {
history_len--;
free(history[history_len]);
return -1;
}
break;
case 20: /* ctrl-t */
if (pos > 0 && pos < len) {
int aux = buf[pos-1];
@@ -432,6 +462,9 @@ up_down_arrow:
pos = len;
refreshLine(fd,prompt,buf,len,pos,cols);
break;
case 12: /* ctrl+l, clear screen */
linenoiseClearScreen();
refreshLine(fd,prompt,buf,len,pos,cols);
}
}
return len;
+3
View File
@@ -4,6 +4,8 @@
* See linenoise.c for more information.
*
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -48,5 +50,6 @@ int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(char *filename);
int linenoiseHistoryLoad(char *filename);
void linenoiseClearScreen(void);
#endif /* __LINENOISE_H */
+2 -3
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>AppendOnlyFileHowto: Contents</b><br>&nbsp;&nbsp;<a href="#Append Only File HOWTO">Append Only File HOWTO</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#General Information">General Information</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Log rewriting">Log rewriting</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Wait... but how does this work?">Wait... but how does this work?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#How durable is the append only file?">How durable is the append only file?</a>
<b>AppendOnlyFileHowto: Contents</b><br>&nbsp;&nbsp;<a href="#Append Only File HOWTO">Append Only File HOWTO</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#General Information">General Information</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Log rewriting">Log rewriting</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Wait... but how does this work?">Wait... but how does this work?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#How durable is the append only file?">How durable is the append only file?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#What should I do if my Append Only File gets corrupted?">What should I do if my Append Only File gets corrupted?</a>
</div>
<h1 class="wikiname">AppendOnlyFileHowto</h1>
@@ -31,8 +31,7 @@
What is the solution? To use append only file as alternative to snapshotting. How it works?<br/><br/><ul><li> It is an 1.1 only feature.</li><li> You have to turn it on editing the configuration file. Just make sure you have &quot;appendonly yes&quot; somewhere.</li><li> Append only files work this way: every time Redis receive a command that changes the dataset (for instance a SET or LPUSH command) it appends this command in the append only file. When you restart Redis it will first <b>re-play</b> the append only file to rebuild the state.</li></ul>
<h2><a name="Log rewriting">Log rewriting</a></h2>As you can guess... the append log file gets bigger and bigger, every time there is a new operation changing the dataset. Even if you set always the same key &quot;mykey&quot; to the values of &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, ... up to 10000000000 in the end you'll have just a single key in the dataset, just a few bytes! but how big will be the append log file? Very very big.<br/><br/>So Redis supports an interesting feature: it is able to rebuild the append log file, in background, without to stop processing client commands. The key is the command <a href="BGREWRITEAOF.html">BGREWRITEAOF</a>. This command basically is able to use the dataset in memory in order to rewrite the shortest sequence of commands able to rebuild the exact dataset that is currently in memory.<br/><br/>So from time to time when the log gets too big, try this command. It's safe as if it fails you will not lost your old log (but you can make a backup copy given that currently 1.1 is still in beta!).<h2><a name="Wait... but how does this work?">Wait... but how does this work?</a></h2>Basically it uses the same fork() copy-on-write trick that snapshotting already uses. This is how the algorithm works:<br/><br/><ul><li> Redis forks, so now we have a child and a parent.</li><li> The child starts writing the new append log file in a temporary file.</li><li> The parent accumulates all the new changes in an in-memory buffer (but at the same time it writes the new changes in the <b>old</b> append only file, so if the rewriting fails, we are safe).</li><li> When the child finished to rewrite the file, the parent gets a signal, and append the in-memory buffer at the end of the file generated by the child.</li><li> Profit! Now Redis atomically renames the old file into the new one, and starts appending new data into the new file.</li></ul>
<h2><a name="How durable is the append only file?">How durable is the append only file?</a></h2>Check redis.conf, you can configure how many times Redis will fsync() data on disk. There are three options:<br/><br/><ul><li> Fsync() every time a new command is appended to the append log file. Very very slow, very safe.</li><li> Fsync() one time every second. Fast enough, and you can lose 1 second of data if there is a disaster.</li><li> Never fsync(), just put your data in the hands of the Operating System. The faster and unsafer method.</li></ul>
Warning: by default Redis will fsync() after <b>every command</b>! This is because the Redis authors want to ship a default configuration that is the safest pick. But the best compromise for most datasets is to fsync() one time every second.
The suggested (and default) policy is &quot;everysec&quot;. It is both very fast and pretty safe. The &quot;always&quot; policy is very slow in practice, even if it was improved in Redis 2.0.0 there is no way to make fsync() faster than it is.<h2><a name="What should I do if my Append Only File gets corrupted?">What should I do if my Append Only File gets corrupted?</a></h2>It is possible that the server crashes while writing the AOF file (this still should never lead to inconsistencies) corrupting the file in a way that is no longer loadable by Redis. When this happens you can fix this problem using the following procedure:<br/><br/><ul><li> Make a backup copy of your AOF file.</li><li> Fix the original file with: ./redis-check-aof --fix <code name="code" class="python">&lt;filename&gt;</code></li><li> Optionally use diff -u to check what is the difference between two files.</li><li> Restart the server with the fixed file.</li></ul>
</div>
</div>
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>BrpoplpushCommand: Contents</b><br>&nbsp;&nbsp;<a href="#BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis &gt;">BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">BrpoplpushCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis &gt;">BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis &gt;</a></h1> 2.1.8) =
<i>Time complexity: O(1)</i><blockquote>Blocking version of the <a href="RpoplpushCommand.html">RPOPLPUSH</a> command. Atomically removes and returnsthe last element (tail) of the source list at <i>srckey</i>, and as a side effect pushes the returned element in the head of the list at <i>dstkey</i>.</blockquote>
If the source list is empty, the client blocks until another client pushes against the source list. Of course in such a case the push operation against the destination list will be performed after the command unblocks detecting a push against the source list.<br/><br/>Note that the command returns an error if the target key already exists but is not a list. The error is delayed at the time the push operation is attempted, that is, immediately if the source list is not empty, or when the first push against the source list happens in the case the command would block.<br/><br/>The timeout value can be 0 or a positive integer value. When it is zero the command will block forever, until something is pushed against <i>srckey</i>. Otherwise the command will wait the specified number of seconds at max, returning an nil value when the timeout expires.<br/><br/>The source and destination of the list can be the same, having the effect of rotating the list. Please check <a href="RpoplpushCommand.html">RPOPLPUSH</a> for more information.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>
</div>
</div>
</div>
</body>
</html>
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ExpireCommand: Contents</b><br>&nbsp;&nbsp;<a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br>&nbsp;&nbsp;<a href="#EXPIREAT _key_ _unixtime_ (Redis &gt;">EXPIREAT _key_ _unixtime_ (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#PERSIST _key_">PERSIST _key_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#How the expire is removed from a key">How the expire is removed from a key</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.0">Version 1.0</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.1">Version 1.1</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?">FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?</a>
<b>ExpireCommand: Contents</b><br>&nbsp;&nbsp;<a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br>&nbsp;&nbsp;<a href="#EXPIREAT _key_ _unixtime_ (Redis &gt;">EXPIREAT _key_ _unixtime_ (Redis &gt;</a><br>&nbsp;&nbsp;<a href="#PERSIST _key_ (Redis &gt;">PERSIST _key_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#How the expire is removed from a key">How the expire is removed from a key</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.0">Version 1.0</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Version 1.1">Version 1.1</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis &lt; 2.1.3 deletes keys with an EXPIRE on write operations?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?">FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?</a>
</div>
<h1 class="wikiname">ExpireCommand</h1>
@@ -28,8 +28,8 @@
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a></h1>
<h1><a name="EXPIREAT _key_ _unixtime_ (Redis &gt;">EXPIREAT _key_ _unixtime_ (Redis &gt;</a></h1> 1.1)=
<h1><a name="PERSIST _key_">PERSIST _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically delete by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
<h1><a name="PERSIST _key_ (Redis &gt;">PERSIST _key_ (Redis &gt;</a></h1> 2.1.3) =
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically deleted by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingexpires and stopping the server does not stop the flow of time as Redisstores on disk the time when the key will no longer be available as Unixtime, and not the remaining seconds.</blockquote>
<blockquote>EXPIREAT works exctly like EXPIRE but instead to get the number of secondsrepresenting the Time To Live of the key as a second argument (that is arelative way of specifing the TTL), it takes an absolute one in the form ofa UNIX timestamp (Number of seconds elapsed since 1 Gen 1970).</blockquote>
<blockquote>EXPIREAT was introduced in order to implement <a href="AppendOnlyFileHowto.html">the Append Only File persistence mode</a>so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
@@ -78,7 +78,7 @@ redis&gt; incr a
(integer) 1
</pre>
I set a key to the value of 100, then set an expire of 360 seconds, and then incremented the key (before the 360 timeout expired of course). The obvious result would be: 101, instead the key is set to the value of 1. Why?
There is a very important reason involving the Append Only File and Replication. Let's rework a bit hour example adding the notion of time to the mix:
There is a very important reason involving the Append Only File and Replication. Let's rework a bit our example adding the notion of time to the mix:
<pre class="codeblock python python python python" name="code">
SET a 100
EXPIRE a 5
@@ -87,7 +87,6 @@ INCR a
</pre>
Imagine a Redis version that does not implement the &quot;Delete keys with an expire set on write operation&quot; semantic.
Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)<h2><a name="FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?">FAQ: How this limitations were solved in Redis versions &gt; 2.1.3?</a></h2>Since Redis 2.1.3 there are no longer restrictions in the use you can do of write commands against volatile keys, still the replication and AOF file are guaranteed to be fully consistent.<br/><br/>In order to obtain a correct behavior without sacrificing consistency now when a key expires, a DEL operation is synthesized in both the AOF file and against all the attached slaves. This way the expiration process is centralized in the master instance, and there is no longer a chance of consistency errors.<br/><br/>However while the slaves while connected to a master will not expire keys independently, they'll still take the full state of the expires existing in the dataset, so when a slave is elected to a master it will be able to expire the keys independently, fully acting as a master.
</div>
</div>
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>GetbitCommand: Contents</b><br>&nbsp;&nbsp;<a href="#GETBIT _key_ _offset_ (Redis &gt;">GETBIT _key_ _offset_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">GetbitCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="GETBIT _key_ _offset_ (Redis &gt;">GETBIT _key_ _offset_ (Redis &gt;</a></h1> 2.1.8) =
<i>Time complexity: O(1)</i><blockquote>Returns the bit value at <i>offset</i> in the string value stored at <i>key</i>.</blockquote>
When <i>offset</i> is beyond the string length, the string is assumed to be a contiguous space with 0 bits. When <i>key</i> does not exist it is assumed to be an empty string, so <i>offset</i> is always out of range and the value is also assumed to be a contiguous space with 0 bits.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the bit value stored at <i>offset</i>.
</div>
</div>
</div>
</body>
</html>
+1 -1
View File
@@ -28,7 +28,7 @@
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HDEL _key_ _field_ (Redis &gt;">HDEL _key_ _field_ (Redis &gt;</a></h1> 1.3.10)=
<i>Time complexity: O(1)</i><blockquote>Remove the specified <i>field</i> from an hash stored at <i>key</i>.</blockquote>
<blockquote>If the <i>field</i> was present in the hash it is returned and 1 is returned, otherwise 0 is returned and no operation is performed.</blockquote>
<blockquote>If the <i>field</i> was present in the hash it is deleted and 1 is returned, otherwise 0 is returned and no operation is performed.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>
</div>
+1 -1
View File
@@ -29,7 +29,7 @@
&iuml;&raquo;&iquest;#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HKEYS _key_ (Redis &gt;">HKEYS _key_ (Redis &gt;</a></h1> 1.3.10)=
<h1><a name="HVALS _key_ (Redis &gt;">HVALS _key_ (Redis &gt;</a></h1> 1.3.10)=
<h1><a name="HGETALL _key_ (Redis &gt;">HGETALL _key_ (Redis &gt;</a></h1> 1.3.10)=
<i>Time complexity: O(N), where N is the total number of entries</i><blockquote>HKEYS returns all the fields names contained into a hash, HVALS all the associated values, while HGETALL returns both the fields and values in the form of <i>field1</i>, <i>value1</i>, <i>field2</i>, <i>value2</i>, ..., <i>fieldN</i>, <i>valueN</i>.</blockquote>
<i>Time complexity: O(N), where N is the total number of fields in the hash</i><blockquote>HKEYS returns all the fields names contained into a hash, HVALS all the associated values, while HGETALL returns both the fields and values in the form of <i>field1</i>, <i>value1</i>, <i>field2</i>, <i>value2</i>, ..., <i>fieldN</i>, <i>valueN</i>.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi Bulk Reply</a>
</div>
+40
View File
@@ -0,0 +1,40 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>HmgetCommand: Contents</b><br>&nbsp;&nbsp;<a href="#HMGET _key_ _field1_ ... _fieldN_ (Redis &gt;">HMGET _key_ _field1_ ... _fieldN_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">HmgetCommand</h1>
<div class="summary">
</div>
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HMGET _key_ _field1_ ... _fieldN_ (Redis &gt;">HMGET _key_ _field1_ ... _fieldN_ (Redis &gt;</a></h1> 1.3.10) =
<i>Time complexity: O(N) (with N being the number of fields)</i><blockquote>Retrieve the values associated to the specified <i>fields</i>.</blockquote>
<blockquote>If some of the specified <i>fields</i> do not exist, nil values are returned.Non existing keys are considered like empty hashes.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi Bulk Reply</a> specifically a list of all the values associated with the specified fields, in the same order of the request.
</div>
</div>
</div>
</body>
</html>
+1 -2
View File
@@ -79,7 +79,7 @@ $ ./redis-cli sismember myset 3
(integer) 1
$ ./redis-cli sismember myset 30
(integer) 0
</pre>&quot;3&quot; is a member of the set, while &quot;30&quot; is not. Sets are very good in order to express relations between objects. For instance we can easily Redis Sets in order to implement tags.<br/><br/>A simple way to model this is to have, for every object you want to tag, a Set with all the IDs of the tags associated with the object, and for every tag that exists, a Set of of all the objects tagged with this tag.<br/><br/>For instance if our news ID 1000 is tagged with tag 1,2,5 and 77, we can specify the following two Sets:<br/><br/><pre class="codeblock python python python python python python python" name="code">
</pre>&quot;3&quot; is a member of the set, while &quot;30&quot; is not. Sets are very good in order to express relations between objects. For instance we can easily use Redis Sets in order to implement tags.<br/><br/>A simple way to model this is to have, for every object you want to tag, a Set with all the IDs of the tags associated with the object, and for every tag that exists, a Set of of all the objects tagged with this tag.<br/><br/>For instance if our news ID 1000 is tagged with tag 1,2,5 and 77, we can specify the following two Sets:<br/><br/><pre class="codeblock python python python python python python python" name="code">
$ ./redis-cli sadd news:1000:tags 1
(integer) 1
$ ./redis-cli sadd news:1000:tags 2
@@ -143,7 +143,6 @@ $ ./redis-cli zrangebyscore hackers -inf 1950
$ ./redis-cli zremrangebyscore hackers 1940 1960
(integer) 2
</pre><a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> is not the best command name, but it can be very useful, and returns the number of removed elements.<h3><a name="Back to the reddit example">Back to the reddit example</a></h3>For the last time, back to the Reddit example. Now we have a decent plan to populate a sorted set in order to generate the home page. A sorted set can contain all the news that are not older than a few days (we remove old entries from time to time using ZREMRANGEBYSCORE). A background job gets all the elements from this sorted set, get the user votes and the time of the news, and compute the score to populate the <b>reddit.home.page</b> sorted set with the news IDs and associated scores. To show the home page we have just to perform a blazingly fast call to ZRANGE.<br/><br/>From time to time we'll remove too old news from the <b>reddit.home.page</b> sorted set as well in order for our system to work always against a limited set of news.<h3><a name="Updating the scores of a sorted set">Updating the scores of a sorted set</a></h3>Just a final note before to finish this tutorial. Sorted sets scores can be updated at any time. Just calling again ZADD against an element already included in the sorted set will update its score (and position) in O(log(N)), so sorted sets are suitable even when there are tons of updates.<br/><br/>This tutorial is in no way complete, this is just the basics to get started with Redis, read the <a href="CommandReference.html">Command Reference</a> to discover a lot more.<br/><br/>Thanks for reading. Salvatore.
</div>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;== List Commands ==<br/><br/><ul><li> <a href="RpushCommand.html">RPUSH</a></li><li> <a href="RpushCommand.html">LPUSH</a></li><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li><li> <a href="LremCommand.html">LREM</a></li><li> <a href="LpopCommand.html">LPOP</a></li><li> <a href="LpopCommand.html">RPOP</a></li><li> <a href="BlpopCommand.html">BLPOP</a></li><li> <a href="BlpopCommand.html">BRPOP</a></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
&iuml;&raquo;&iquest;== List Commands ==<br/><br/><ul><li> <a href="RpushCommand.html">RPUSH</a></li><li> <a href="RpushCommand.html">LPUSH</a></li><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li><li> <a href="LremCommand.html">LREM</a></li><li> <a href="LpopCommand.html">LPOP</a></li><li> <a href="LpopCommand.html">RPOP</a></li><li> <a href="BlpopCommand.html">BLPOP</a></li><li> <a href="BlpopCommand.html">BRPOP</a></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a></li><li> <a href="BrpoplpushCommand.html">BRPOPLPUSH</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
</div>
</div>
+6 -7
View File
@@ -32,12 +32,12 @@
<h1><a name="COMMAND_1 ...">COMMAND_1 ...</a></h1>
<h1><a name="COMMAND_2 ...">COMMAND_2 ...</a></h1>
<h1><a name="COMMAND_N ...">COMMAND_N ...</a></h1>
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the fundation of Redis Transactions.
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the foundation of Redis Transactions.
A Redis Transaction allows the execution of a group of Redis commands in a single
step, with two important guarantees:<br/><br/><ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
Since Redis 2.1.0, it's also possible to add a further guarantee to the above two, in the form of optimistic locking of a set of keys in a way very similar to a CAS (check and set) operation. This is documented later in this manual page.<h2><a name="Usage">Usage</a></h2>A Redis transaction is entered using the MULTI command. The command always
replies with OK. At this point the user can issue multiple commands. Instead
to execute this commands Redis will &quot;queue&quot; them. All the commands are
of executing these commands, Redis will &quot;queue&quot; them. All the commands are
executed once EXEC is called.<br/><br/>Calling DISCARD instead will flush the transaction queue and will exit
the transaction.<br/><br/>The following is an example using the Ruby client:
<pre class="codeblock python" name="code">
@@ -57,7 +57,7 @@ replies, where every element is the reply of a single command in the
transaction, in the same order the commands were queued.<br/><br/>When a Redis connection is in the context of a MULTI request, all the commands
will reply with a simple string &quot;QUEUED&quot; if they are correct from the
point of view of the syntax and arity (number of arguments) of the commaand.
Some command is still allowed to fail during execution time.<br/><br/>This is more clear if at protocol level: in the following example one command
Some commands are still allowed to fail during execution time.<br/><br/>This is more clear on the protocol level; In the following example one command
will fail when executed even if the syntax is right:
<pre class="codeblock python python" name="code">
Trying 127.0.0.1...
@@ -75,7 +75,7 @@ EXEC
+OK
-ERR Operation against a key holding the wrong kind of value
</pre>
MULTI returned a two elements bulk reply in witch one of this is a +OK
MULTI returned a two elements bulk reply where one is an +OK
code and one is a -ERR reply. It's up to the client lib to find a sensible
way to provide the error to the user.<br/><br/><blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
Another example, again using the write protocol with telnet, shows how
@@ -87,9 +87,8 @@ INCR a b c
-ERR wrong number of arguments for 'incr' command
</pre>
This time due to the syntax error the &quot;bad&quot; INCR command is not queued
at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be
executed, and the state of the client is again the normal one, outside
<blockquote>of a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be executed, and the state of the client is again the normal one, outside of a transaction. Example using the Ruby client:
<pre class="codeblock python python python python" name="code">
?&gt; r.set(&quot;foo&quot;,1)
=&gt; true
&gt;&gt; r.multi
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>NonexistentCommands: Contents</b><br>&nbsp;&nbsp;<a href="#HGETSET">HGETSET</a><br>&nbsp;&nbsp;<a href="#SET with expire">SET with expire</a><br>&nbsp;&nbsp;<a href="#ZADDNX">ZADDNX</a>
</div>
<h1 class="wikiname">NonexistentCommands</h1>
<div class="summary">
A list of commands that don't exist in Redis, but can be accomplished in a different way.
</div>
<div class="narrow">
This is a list of commands that don't exist in Redis, but can be accomplished in a different way, usually by means of <a href="MultiExecCommand.html">WATCH/MULTI/EXEC</a>.<br/><br/>For better performance, you can pipeline multiple commands.<h1><a name="HGETSET">HGETSET</a></h1><a href="GetsetCommand.html">GETSET</a> for Hashes.<br/><br/><pre class="codeblock python" name="code">
WATCH foo
old_value = HGET foo field
MULTI
HSET foo field new_value
EXEC
</pre><h1><a name="SET with expire">SET with expire</a></h1>See <a href="SetexCommand.html">SETEX</a>.<h1><a name="ZADDNX">ZADDNX</a></h1>Add an element to a sorted set, only if the element doesn't already exist (by default, <a href="ZaddCommand.html">ZADD</a> would update the element's score if it already exists). <a href="http://groups.google.com/group/redis-db/browse_thread/thread/fc4c79d72e5bd346/6cdc07ecc36b81e7" target="_blank">See thread</a>.<br/><br/><pre class="codeblock python python" name="code">
WATCH foo
score = ZSCORE foo bar
IF score != NIL
MULTI
ZADD foo 1 bar
EXEC
ENDIF
</pre>
</div>
</div>
</div>
</body>
</html>
+69 -74
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>ProtocolSpecification: Contents</b><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Networking layer">Networking layer</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Simple INLINE commands">Simple INLINE commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk commands">Bulk commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk replies">Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies">Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Single line reply">Single line reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Integer reply">Integer reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi bulk commands">Multi bulk commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple commands and pipelining">Multiple commands and pipelining</a>
<b>ProtocolSpecification: Contents</b><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Networking layer">Networking layer</a><br>&nbsp;&nbsp;<a href="#Requests">Requests</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#The new unified request protocol">The new unified request protocol</a><br>&nbsp;&nbsp;<a href="#Replies">Replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Single line reply">Single line reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Error reply">Error reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Integer reply">Integer reply</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk replies">Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multi-Bulk replies">Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Multiple commands and pipelining">Multiple commands and pipelining</a><br>&nbsp;&nbsp;<a href="#The old protocol for sending commands">The old protocol for sending commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Inline Commands">Inline Commands</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Bulk commands">Bulk commands</a>
</div>
<h1 class="wikiname">ProtocolSpecification</h1>
@@ -26,53 +26,54 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;= Protocol Specification =<br/><br/>The Redis protocol is a compromise between being easy to parse by a computer
and being easy to parse by an human. Before reading this section you are
strongly encouraged to read the &quot;REDIS TUTORIAL&quot; section of this README in order
to get a first feeling of the protocol playing with it by TELNET.<h2><a name="Networking layer">Networking layer</a></h2>A client connects to a Redis server creating a TCP connection to the port 6379.
Every redis command or data transmitted by the client and the server is
terminated by &quot;\r\n&quot; (CRLF).<h2><a name="Simple INLINE commands">Simple INLINE commands</a></h2>The simplest commands are the inline commands. This is an example of a
server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code">
C: PING
S: +PONG
</pre>An inline command is a CRLF-terminated string sent to the client. The server can reply to commands in different ways:
<ul><li> With an error message (the first byte of the reply will be &quot;-&quot;)</li><li> With a single line reply (the first byte of the reply will be &quot;+)</li><li> With bulk data (the first byte of the reply will be &quot;$&quot;)</li><li> With multi-bulk data, a list of values (the first byte of the reply will be &quot;<code name="code" class="python">*</code>&quot;)</li><li> With an integer number (the first byte of the reply will be &quot;:&quot;)</li></ul>
The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python" name="code">
C: EXISTS somekey
S: :0
</pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
simply by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>A bulk command is exactly like an inline command, but the last argument
of the command must be a stream of bytes in order to send data to the server.
the &quot;SET&quot; command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code">
C: SET mykey 6
C: foobar
S: +OK
</pre>The last argument of the commnad is '6'. This specify the number of DATA
bytes that will follow (note that even this bytes are terminated by two
additional bytes of CRLF).<br/><br/>All the bulk commands are in this exact form: instead of the last argument
the number of bytes that will follow is specified, followed by the bytes,
and CRLF. In order to be more clear for the programmer this is the string
sent by the client in the above sample:<br/><br/><blockquote>&quot;SET mykey 6\r\nfoobar\r\n&quot;</blockquote>
<h2><a name="Bulk replies">Bulk replies</a></h2>The server may reply to an inline or bulk command with a bulk reply. See
the following example:<br/><br/><pre class="codeblock python python python python" name="code">
&iuml;&raquo;&iquest;= Protocol Specification =<br/><br/>The Redis protocol is a compromise between the following things:<br/><br/><ul><li> Simple to implement.</li><li> Fast to parse by a computer.</li><li> Easy enough to parse by a human.</li></ul>
<h2><a name="Networking layer">Networking layer</a></h2>A client connects to a Redis server creating a TCP connection to the port 6379.
Every Redis command or data transmitted by the client and the server is
terminated by &quot;\r\n&quot; (CRLF).<h1><a name="Requests">Requests</a></h1>Redis accepts commands composed of different arguments.
Once a command is received, it is processed and a reply is sent back to the client.<h2><a name="The new unified request protocol">The new unified request protocol</a></h2>The new unified protocol was introduced in Redis 1.2, but it became the standard way for talking with the Redis server in Redis 2.0.<br/><br/>In the unified protocol all the arguments sent to the Redis server are binary safe. This is the general form:<br/><br/><pre class="codeblock python" name="code">
*&lt;number of arguments&gt; CR LF
$&lt;number of bytes of argument 1&gt; CR LF
&lt;argument data&gt; CR LF
...
$&lt;number of bytes of argument N&gt; CR LF
&lt;argument data&gt; CR LF
</pre>See the following example:<br/><br/><pre class="codeblock python python" name="code">
*3
$3
SET
$5
mykey
$7
myvalue
</pre>This is how the above command looks as a quoted string, so that it is possible to see the exact value of every byte in the query:<br/><br/><pre class="codeblock python python python" name="code">
&quot;*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$8\r\nmyvalue\r\n&quot;
</pre>As you will see in a moment this format is also used in Redis replies.
The format used for every argument &quot;$6\r\nmydata\r\n&quot; is called a Bulk Reply.
While the actual unified request protocol is what Redis uses to return list of items, and is called a Multi Bulk Reply. It is just the sum of N different
Bulk Replies prefixed by a <code name="code" class="python">*&lt;argc&gt;\r\n</code> string where <code name="code" class="python">&lt;argc&gt;</code> is the number of arguments (Bulk Replies) that will follow.<h1><a name="Replies">Replies</a></h1>Redis will reply to commands with different kinds of replies. It is possible to check the kind of reply from the first byte sent by the server:<br/><br/><ul><li> With a single line reply the first byte of the reply will be &quot;+&quot;</li><li> With an error message the first byte of the reply will be &quot;-&quot;</li><li> With an integer number the first byte of the reply will be &quot;:&quot;</li><li> With bulk reply the first byte of the reply will be &quot;$&quot;</li><li> With multi-bulk reply the first byte of the reply will be &quot;<code name="code" class="python">*</code>&quot;</li></ul>
<h2><a name="Single line reply">Single line reply</a></h2>A single line reply is in the form of a single line string
starting with &quot;+&quot; terminated by &quot;\r\n&quot;. For example:<br/><br/><pre class="codeblock python python python python" name="code">
+OK
</pre>The client library should return everything after the &quot;+&quot;, that is, the string &quot;OK&quot; in the example.<br/><br/>The following commands reply with a single line reply:
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Error reply">Error reply</a></h2>Errors are sent exactly like Single Line Replies. The only difference is that the first byte is &quot;-&quot; instead of &quot;+&quot;.<br/><br/>Error replies are only sent when something strange happened, for instance if you try to perform an operation against the wrong data type, or if the command does not exist and so forth. So an exception should be raised by the library client when an Error Reply is received.<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a &quot;:&quot; byte. For example &quot;:0\r\n&quot;, or &quot;:1000\r\n&quot; are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<h2><a name="Bulk replies">Bulk replies</a></h2>Bulk replies are used by the server in order to return a single binary safe string.<br/><br/><pre class="codeblock python python python python python" name="code">
C: GET mykey
S: $6
S: foobar
</pre>A bulk reply is very similar to the last argument of a bulk command. The
server sends as the first line a &quot;$&quot; byte followed by the number of bytes
of the actual reply followed by CRLF, then the bytes are sent followed by
additional two bytes for the final CRLF. The exact sequence sent by the
server is:<br/><br/><blockquote>&quot;$6\r\nfoobar\r\n&quot;</blockquote>
If the requested value does not exist the bulk reply will use the special
value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python" name="code">
</pre>The server sends as the first line a &quot;$&quot; byte followed by the number of bytes
of the actual reply, followed by CRLF, then the actual data bytes are sent,
followed by additional two bytes for the final CRLF.
The exact sequence sent by the server is:<br/><br/><pre class="codeblock python python python python python python" name="code">
&quot;$6\r\nfoobar\r\n&quot;
</pre>If the requested value does not exist the bulk reply will use the special
value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
C: GET nonexistingkey
S: $-1
</pre>The client library API should not return an empty string, but a nil object, when the requested object does not exist.
For example a Ruby library should return 'nil' while a C library should return
NULL, and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands similar to LRANGE needs to return multiple values (every element
NULL (or set a special flag in the reply object), and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands like LRANGE need to return multiple values (every element
of the list is a value, and LRANGE needs to return more than a single element). This is accomplished using multiple bulk writes,
prefixed by an initial line indicating how many bulk writes will follow.
The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python" name="code">
The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
C: LRANGE mylist 0 3
S: *4
S: $3
@@ -83,56 +84,50 @@ S: $5
S: Hello
S: $5
S: World
</pre>The first line the server sent is &quot;<b>4\r\n&quot; in order to specify that four bulk
write will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist instead of the number of elements in the
list, the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
</pre>As you can see the multi bulk reply is exactly the same format used in order
to send commands to the Redis server unsing the unified protocol.<br/><br/>The first line the server sent is &quot;<b>4\r\n&quot; in order to specify that four bulk
replies will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist, instead of the number of elements in the
list the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
C: LRANGE nokey 0 1
S: *-1
</pre>A client library API SHOULD return a nil object and not an empty list when this
happens. This makes possible to distinguish between empty list and non existing ones.<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
happens. This makes possible to distinguish between empty list and other error conditions (for instance a timeout condition in the BLPOP command).<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
S: *3
S: $3
S: foo
S: $-1
S: $3
S: bar
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code">
[&quot;foo&quot;,nil,&quot;bar&quot;]
</pre><h2><a name="Single line reply">Single line reply</a></h2>As already seen a single line reply is in the form of a single line string
starting with &quot;+&quot; terminated by &quot;\r\n&quot;. For example:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
+OK
</pre>The client library should return everything after the &quot;+&quot;, that is, the string &quot;OK&quot; in the example.<br/><br/>The following commands reply with a status code reply:
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a &quot;:&quot; byte. For example &quot;:0\r\n&quot;, or &quot;:1000\r\n&quot; are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<h2><a name="Multi bulk commands">Multi bulk commands</a></h2>As you can see with the protocol described so far there is no way to
send multiple binary-safe arguments to a command. With bulk commands the
last argument is binary safe, but there are commands where multiple binary-safe
commands are needed, like the MSET command that is able to SET multiple keys
in a single operation.<br/><br/>In order to address this problem Redis 1.1 introduced a new way of seding
commands to a Redis server, that uses exactly the same protocol of the
multi bulk replies. For instance the following is a SET command using the
normal bulk protocol:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code">
SET mykey 8
myvalue
</pre>While the following uses the multi bulk command protocol:<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
*3
$3
SET
$5
mykey
$8
myvalue
</pre>Commands sent in this format are longer, so currently they are used only in
order to transmit commands containing multiple binary-safe arguments, but
actually this protocol can be used to send every kind of command, without to
know if it's an inline, bulk or multi-bulk command.<br/><br/>It is possible that in the future Redis will support only this format.<br/><br/>A good client library may implement unknown commands using this
command format in order to support new commands out of the box without
modifications.<h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
</pre><h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
Pipelining is supported so multiple commands can be sent with a single
write operation by the client, it is not needed to read the server reply
in order to issue the next command. All the replies can be read at the end.<br/><br/>Usually Redis server and client will have a very fast link so this is not
very important to support this feature in a client implementation, still
if an application needs to issue a very large number of commands in short
time to use pipelining can be much faster.
</b>
time to use pipelining can be much faster.<h1><a name="The old protocol for sending commands">The old protocol for sending commands</a></h1>Before of the Unified Request Protocol Redis used a different protocol to send
commands, that is still supported since it is simpler to type by hand via telnet. In this protocol there are two kind of commands:<br/><br/><blockquote>* Inline commands: simple commands where argumnets are just space separated strings. No binary safeness is possible.* Bulk commands: bulk commands are exactly like inline commands, but the last argument is handled in a special way in order to allow for a binary-safe last argument.</blockquote>
<h2><a name="Inline Commands">Inline Commands</a></h2>The simplest way to send Redis a command is via </b>Inline Commands.
The following is an example of a server/client chat using an inline command (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
C: PING
S: +PONG
</pre>The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python python python python python python python python python python python python" name="code">
C: EXISTS somekey
S: :0
</pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>Some commands when sent as inline commands require a special form in order
to support a binary safe last argument. This commands will use the last argument
for a &quot;byte count&quot;, then the bulk data is sent (that can be binary safe since
the server knows how many bytes to read).<br/><br/>See for instance the following example:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python" name="code">
C: SET mykey 6
C: foobar
S: +OK
</pre>The last argument of the commnad is '6'. This specify the number of DATA
bytes that will follow, that is, the string &quot;foobar&quot;. Note that even this bytes are terminated by two additional bytes of CRLF.<br/><br/>All the bulk commands are in this exact form: instead of the last argument
the number of bytes that will follow is specified, followed by the bytes
composing the argument itself, and CRLF. In order to be more clear for the programmer this is the string sent by the client in the above sample:<br/><br/><blockquote>&quot;SET mykey 6\r\nfoobar\r\n&quot;</blockquote>
Redis has an internal list of what command is inline and what command is bulk, so you have to send this commands accordingly. It is strongly suggested to use the new Unified Request Protocol instead.
</div>
</div>
+1 -2
View File
@@ -32,7 +32,7 @@
<h1><a name="PSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a></h1>
<h1><a name="PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a></h1>
<h1><a name="PUNSUBSCRIBE (unsubscribe from all patterns)">PUNSUBSCRIBE (unsubscribe from all patterns)</a></h1>
<h1><a name="PUBLISH channel message">PUBLISH channel message</a></h1>Time complexity: subscribe is O(1), unsubscribe is O(N) where N is the number of clients already subscribed to a channel, publish is O(N+M) where N is the number of clients subscribed to the receiving channel, and M is the total number of subscribed patterns (by any client). Psubscribe is O(N) where N is the number of patterns the Psubscribing client is already subscribed to. Punsubscribe is O(N+M) where N is the number of patterns the Punsubscribing client is already subscribed and M is the number of total patterns subscribed in the system (by any client).<br/><br/><blockquote>SUBSCRIBE, UNSUBSCRIBE and PUBLISH commands implement the<a href="http://en.wikipedia.org/wiki/Publish/subscribe" target="_blank">Publish/Subscribe messaging paradigm</a> where (citing Wikipedia) senders (publishers) are not programmed to send their messages to specific receivers (subscribers). Rather, published messages are characterized into channels, without knowledge of what (if any) subscribers there may be. Subscribers express interest in one or more channels, and only receive messages that are of interest, without knowledge of what (if any) publishers there are. This decoupling of publishers and subscribers can allow for greater scalability and a more dynamic network topology.</blockquote>
<h1><a name="PUBLISH channel message">PUBLISH channel message</a></h1>Time complexity: subscribe is O(1), unsubscribe is O(N) where N is the number of clients already subscribed to a channel, publish is O(N+M) where N is the number of clients subscribed to the receiving channel, and M is the total number of subscribed patterns (by any client). Psubscribe is O(N) where N is the number of patterns the Psubscribing client is already subscribed to. Punsubscribe is O(N+M) where N is the number of patterns the Punsubscribing client is already subscribed and M is the number of total patterns subscribed in the system (by any client).<br/><br/><b>Note</b>: this commands are available starting form Redis 2.0.0<br/><br/><blockquote>SUBSCRIBE, UNSUBSCRIBE and PUBLISH commands implement the<a href="http://en.wikipedia.org/wiki/Publish/subscribe" target="_blank">Publish/Subscribe messaging paradigm</a> where (citing Wikipedia) senders (publishers) are not programmed to send their messages to specific receivers (subscribers). Rather, published messages are characterized into channels, without knowledge of what (if any) subscribers there may be. Subscribers express interest in one or more channels, and only receive messages that are of interest, without knowledge of what (if any) publishers there are. This decoupling of publishers and subscribers can allow for greater scalability and a more dynamic network topology.</blockquote>
<blockquote>For instance in order to subscribe to the channels foo and bar the clientwill issue the SUBSCRIBE command followed by the names of the channels.</blockquote><pre class="codeblock python" name="code">
SUBSCRIBE foo bar
</pre>
@@ -106,7 +106,6 @@ The Publish command is a bulk command where the first argument is the target cla
Pieter Noordhuis provided a great example using Event-machine and Redis to create <a href="http://chat.redis-db.com" target="_blank">a multi user high performance web chat</a>, with source code included of course!
<h2><a name="Client library implementations hints">Client library implementations hints</a></h2>
Because all the messages received contain the original subscription causing the message delivery (the channel in the case of &quot;message&quot; type, and the original pattern in the case of &quot;pmessage&quot; type) clinet libraries may bind the original subscription to callbacks (that can be anonymous functions, blocks, function pointers, and so forth), using an hash table.<br/><br/>When a message is received an O(1) lookup can be done in order to deliver the message to the registered callback.
</div>
</div>
+37
View File
@@ -0,0 +1,37 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>RedisCLI: Contents</b><br>&nbsp;&nbsp;<a href="#Redis CLI">Redis CLI</a>
</div>
<h1 class="wikiname">RedisCLI</h1>
<div class="summary">
Redis Command Line Interface
</div>
<div class="narrow">
<h1><a name="Redis CLI">Redis CLI</a></h1>
</div>
</div>
</div>
</body>
</html>
+93
View File
@@ -0,0 +1,93 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>RedisPipelining: Contents</b><br>&nbsp;&nbsp;<a href="#Request/Response protocols and RTT">Request/Response protocols and RTT</a><br>&nbsp;&nbsp;<a href="#Redis Pipelining">Redis Pipelining</a><br>&nbsp;&nbsp;<a href="#Some benchmark">Some benchmark</a><br>&nbsp;&nbsp;<a href="#Pipelining VS other multi-commands">Pipelining VS other multi-commands</a>
</div>
<h1 class="wikiname">RedisPipelining</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Request/Response protocols and RTT">Request/Response protocols and RTT</a></h1>
Redis is a TCP server using the client-server model and what is called a <b>Request/Response</b> protocol.<br/><br/>This means that usually a request is accomplished with the following steps:
<ul><li> The client sends a query to the server, and reads from the socket, usually in a blocking way, for the server response.</li><li> The server processes the command and sends the response back to the server.</li></ul>So for instance a four commands sequence is something like this:
<ul><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 1</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 2</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 3</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 4</li></ul>Clients and Servers are connected via a networking link. Such a link can be very fast (a loopback interface) or very slow (a connection established over the internet with many hops between the two hosts). Whatever the network latency is, there is a time for the packets to travel from the client to the server, and back from the server to the client to carry the reply.<br/><br/>This time is called RTT (Round Trip Time). It is very easy to see how this can affect the performances when a client needs to perform many requests in a row (for instance adding many elements to the same list, or populating a database with many keys). For instance if the RTT time is 250 milliseconds (in the case of a very slow link over the internet), even if the server is able to process 100k requests per second, we'll be able to process at max four requests per second.<br/><br/>If the interface used is a loopback interface, the RTT is much shorter (for instance my host reports 0,044 milliseconds pinging 127.0.0.1), but it is still a lot if you need to perform many writes in a row.<br/><br/>Fortunately there is a way to improve this use cases.
<h1><a name="Redis Pipelining">Redis Pipelining</a></h1>
A Request/Response server can be implemented so that it is able to process new requests even if the client didn't already read the old responses. This way it is possible to send <b>multiple commands</b> to the server without waiting for the replies at all, and finally read the replies in a single step.<br/><br/>This is called pipelining, and is a technique widely in use since many decades. For instance many POP3 protocol implementations already supported this feature, dramatically speeding up the process of downloading new emails from the server.<br/><br/>Redis supports pipelining since the very early days, so whatever version you are running, you can use pipelining with Redis. This is an example using the raw netcat utility:
<pre class="codeblock python" name="code">
$ (echo -en &quot;PING\r\nPING\r\nPING\r\n&quot;; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
</pre>
This time we are not paying the cost of RTT for every call, but just one time for the three commands.<br/><br/>To be very explicit, with pipelining the order of operations of our very first example will be the following:
<ul><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 1</li><li> <b>Server:</b> 2</li><li> <b>Server:</b> 3</li><li> <b>Server:</b> 4</li></ul><b>IMPORTANT NOTE</b>: while the client sends commands using pipelining, the server will be forced to queue the replies, using memory. So if you need to send many many commands with pipelining it's better to send this commands up to a given reasonable number, for instance 10k commands, read the replies, and send again other 10k commands and so forth. The speed will be nearly the same, but the additional memory used will be at max the amount needed to queue the replies for this 10k commands.
<h1><a name="Some benchmark">Some benchmark</a></h1>
In the following benchmark we'll use the Redis Ruby client, supporting pipelining, to test the speed improvement due to pipelining:
<pre class="codeblock python python" name="code">
require 'rubygems'
require 'redis'
def bench(descr)
start = Time.now
yield
puts &quot;#{descr} #{Time.now-start} seconds&quot;
end
def without_pipelining
r = Redis.new
10000.times {
r.ping
}
end
def with_pipelining
r = Redis.new
r.pipelined {
10000.times {
r.ping
}
}
end
bench(&quot;without pipelining&quot;) {
without_pipelining
}
bench(&quot;with pipelining&quot;) {
with_pipelining
}
</pre>
Running the above simple script will provide this figures in my Mac OS X system, running over the loopback interface, where pipelining will provide the smallest improvement as the RTT is already pretty low:
<pre class="codeblock python python python" name="code">
without pipelining 1.185238 seconds
with pipelining 0.250783 seconds
</pre>
As you can see using pipelining we improved the transfer by a factor of five.
<h1><a name="Pipelining VS other multi-commands">Pipelining VS other multi-commands</a></h1>
Often we get requests about adding new commands performing multiple operations in a single pass.
For instance there is no command to add multiple elements in a set. You need calling many times SADD.<br/><br/>With pipelining you can have performances near to an MSADD command, but at the same time we'll avoid bloating the Redis command set with too many commands. An additional advantage is that the version written using just SADD will be ready for a distributed environment (for instance Redis Cluster, that is in the process of being developed) just dropping the pipelining code.
</div>
</div>
</div>
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>RedisStatus: Contents</b><br>&nbsp;&nbsp;<a href="#Redis Status Page">Redis Status Page</a><br>&nbsp;&nbsp;<a href="#How stable are the alpha previews?">How stable are the alpha previews?</a><br>&nbsp;&nbsp;<a href="#How to obtain a 2.2-alpha preview">How to obtain a 2.2-alpha preview</a><br>&nbsp;&nbsp;<a href="#ETA for Redis 2.2?">ETA for Redis 2.2?</a><br>&nbsp;&nbsp;<a href="#When will we be able to see a working version of Redis Cluster?">When will we be able to see a working version of Redis Cluster?</a>
</div>
<h1 class="wikiname">RedisStatus</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Redis Status Page">Redis Status Page</a></h1>Hello! Redis uses versions composed of three numbers separated by a dot: <b>major</b>.<b>minor</b>.<b>patchlevel</b>.<br/><br/>When the <b>minor</b> is an odd number, it is used for an unstable release, so stable releases are for instance 1.2, 2.0, and so forth.<br/><br/>This is the status of the different Redis versions currently available:<br/><br/><ul><li> 1.2 is the <b>legacy redis stable release</b>, now it is completely obsoleted by Redis 2.0. Redis 2.0 is almost completely back compatible with 1.2 so upgrading is usually not a problem. Still 1.2 is believed to be a very stable release that works well, so if you are using it in production with code that probably will not modified to use more advanced Redis features available in 2.0, it makes sense to take 1.2 running. For everything new, it's better to start with 2.0.</li></ul>
<ul><li> 2.0 is the current <b>stable release</b>. It is better than 1.2 in more or less everything: more features, more mature code, better replication, better persistence, and so forth. It is currently what most users should use, unless they really need features that are only available into an <b>unstable</b> release.</li></ul>
<ul><li> 2.1 is the current <b>unstable release</b>, and there are no tar.gz for this release, you need to download it from git. <b>Warning:</b> the master branch in git may work most of the time but is NOT what you should use. What's better instead is to use the 2.2-alpha tags: every time Redis 2.1.x is stable enough and the new features merged passed all the tests for a couple of weeks, and we didn't received severe bug reports from users, we tag master as 2.2-alpha <i>number</i>, where <i>number</i> is simply a progressive number. Just pick this number.</li></ul>
<h1><a name="How stable are the alpha previews?">How stable are the alpha previews?</a></h1>
Well it is surely ok for development, but it is not recommended for production. Still there are many users that trust Redis development process so much to use alpha releases in production, but this is up to you, we don't give any guarantee ;)<h1><a name="How to obtain a 2.2-alpha preview">How to obtain a 2.2-alpha preview</a></h1>Simply using git:
<pre class="codeblock python" name="code">
$ git clone git://github.com/antirez/redis.git
Initialized empty Git repository in /tmp/redis/.git/
...
</pre>
Then you can list all the branches matching 2.1-alpha with:
<pre class="codeblock python python" name="code">
cd redis
$ git tag | grep 2.2-alpha
2.2-alpha0
2.2-alpha1
2.2-alpha2
</pre>
At this point you can just use <b>git checkout <i>tagname</i></b>, substituting <i>tagname</i> with 2.2-alphaX where X is the greater progressive number you see in the listing.<h1><a name="ETA for Redis 2.2?">ETA for Redis 2.2?</a></h1>
Redis 2.2 is planned to enter the release candidate stage before the end of the 2010.<h1><a name="When will we be able to see a working version of Redis Cluster?">When will we be able to see a working version of Redis Cluster?</a></h1>
I'm already working at it, I mean not just designing, but writing code. In three months we should have some kind of experimental version, while in six months we should have the first release candidate.<br/><br/>Probably the first <b>stable</b> release of Redis with working cluster will be called 3.0, but I'll try to merge it into 2.2 as an experimental support if we'll be sure there is no impact in the stability of the system when clustering is not used.
</div>
</div>
</div>
</body>
</html>
+62
View File
@@ -0,0 +1,62 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>Redis_2_0_0_Changelog: Contents</b><br>&nbsp;&nbsp;<a href="#Redis 2.0: What's new?">Redis 2.0: What's new?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#MULTI/EXEC">MULTI/EXEC</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Blocking pop">Blocking pop</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Publish/subscribe">Publish/subscribe</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Hashes">Hashes</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Virtual Memory">Virtual Memory</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Contributors">Contributors</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Special Thanks">Special Thanks</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#DOWNLOAD">DOWNLOAD</a>
</div>
<h1 class="wikiname">Redis_2_0_0_Changelog</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Redis 2.0: What's new?">Redis 2.0: What's new?</a></h1>The release of Redis 2.0 marks a major milestone in Redis development. Apart from an endless list of new features, there are some major ones that deserve to be highlighted.<br/><br/>It's worth to mention that while Redis 2.0.0 just reached its first stable release, Redis 2.2.0 is near to reach feature freeze, so ... be prepared for new exiting things in very short time!<h3><a name="MULTI/EXEC">MULTI/EXEC</a></h3>
The MULTI/EXEC family of commands were added to fulfill the need to execute multiple commands as a single atomic block.
Because all commands inside a MULTI/EXEC block are serialized and executed sequentially, it is not possible that another
client request is served in the middle of executing this block. All commands are executed one after the other when
EXEC is called, which makes sure either <b>all</b> or <b>no</b> commands are executed, independent of the state of the client connection.<br/><br/>More on MULTI/EXEC:
<ul><li> <a href="http://code.google.com/p/redis/wiki/MultiExecCommand" target="_blank">http://code.google.com/p/redis/wiki/MultiExecCommand</a></li></ul>
Note that WATCH, a CAS (check and set) variant of MULTI/EXEC will be available on 2.2.0 and is not part of 2.0.0.<h3><a name="Blocking pop">Blocking pop</a></h3>
The commands BLPOP and BRPOP were added to support popping from a list in a blocking fashion. This means the client
connection will be blocked for a certain amount of time until another client pushes an item on a list. These commands
are frequently used in producer/consumer scenarios.<br/><br/>More on blocking pop:
<ul><li> <a href="http://code.google.com/p/redis/wiki/BlpopCommand" target="_blank">http://code.google.com/p/redis/wiki/BlpopCommand</a></li></ul>
<h3><a name="Publish/subscribe">Publish/subscribe</a></h3>
The family of publish/subscribe commands let clients publish messages onto channels and subscribe to receive all messages
that are published on channels. Also included are commands to receive all messages for which the channel matches a given pattern.<br/><br/>More on publish/subscribe:
<ul><li> <a href="http://code.google.com/p/redis/wiki/PublishSubscribe" target="_blank">http://code.google.com/p/redis/wiki/PublishSubscribe</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-3-publish-submit.html" target="_blank">http://antirez.com/post/redis-weekly-update-3-publish-submit.html</a></li><li> <a href="http://rediscookbook.org/pubsub_for_asynchronous_communication.html" target="_blank">http://rediscookbook.org/pubsub_for_asynchronous_communication.html</a></li></ul>
<h3><a name="Hashes">Hashes</a></h3>
This new datatype allows to store multiple key/value pairs on a single key. Together with the list of regular commands you
would expect for such a datatype (HSET, HGET, HDEL, HLEN, HKEYS, ...), it is also possible to use the values <i>inside</i> a hash
for any SORT operation.<br/><br/>More on hashes:
<ul><li> <a href="http://code.google.com/p/redis/wiki/HsetCommand" target="_blank">http://code.google.com/p/redis/wiki/HsetCommand</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-1.html" target="_blank">http://antirez.com/post/redis-weekly-update-1.html</a></li></ul>
<h3><a name="Virtual Memory">Virtual Memory</a></h3>
Redis Virtual Memory allows users to grow their dataset beyond the limits of their RAM.<br/><br/>More on virtual memory:
<ul><li> <a href="http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide" target="_blank">http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide</a></li><li> <a href="http://antirez.com/post/redis-virtual-memory-story.html" target="_blank">http://antirez.com/post/redis-virtual-memory-story.html</a></li></ul>
<h3><a name="Contributors">Contributors</a></h3><ul><li> Salvatore Sanfilippo</li><li> Pieter Noordhuis</li><li> Antonio Ognio</li><li> Alex McHale</li><li> Michel Martens</li><li> Damian Janowski</li><li> Bruno Deferrari</li><li> Ashley Martens</li><li> Derek Collison</li><li> Damian Janowski</li><li> Jeremy Zawodny</li><li> Konstantin Merenkov</li><li> Michel Martens</li><li> Sam Hendley</li></ul>
<h3><a name="Special Thanks">Special Thanks</a></h3>
Thanks to VMware sponsoring the work of Salvatore and Pieter, and the Redis community of users and client library developers. Redis 2.0.0 was possible only thanks to your support.<h3><a name="DOWNLOAD">DOWNLOAD</a></h3>
You can grab Redis 2.0.0 from <a href="http://code.google.com/p/redis/downloads/list" target="_blank">Google Code</a>.<br/><br/>It is also tagged on Git.
</div>
</div>
</div>
</body>
</html>
+59
View File
@@ -0,0 +1,59 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>Redis_2_0_Whats_new: Contents</b><br>&nbsp;&nbsp;<a href="#Redis 2.0: What's new?">Redis 2.0: What's new?</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#MULTI/EXEC">MULTI/EXEC</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Blocking pop">Blocking pop</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Publish/subscribe">Publish/subscribe</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Hashes">Hashes</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Virtual Memory">Virtual Memory</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Contributors">Contributors</a>
</div>
<h1 class="wikiname">Redis_2_0_Whats_new</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="Redis 2.0: What's new?">Redis 2.0: What's new?</a></h1>The release of Redis 2.0 marks a major milestone in Redis development. Apart from an endless list of new features, there are some major ones that deserve to be highlighted.<h3><a name="MULTI/EXEC">MULTI/EXEC</a></h3>
The MULTI/EXEC family of commands were added to fulfill the need to execute multiple commands as a single atomic block.
Because all commands inside a MULTI/EXEC block are serialized and executed sequentially, it is not possible that another
client request is served in the middle of executing this block. All commands are executed one after the other when
EXEC is called, which makes sure either <b>all</b> or <b>no</b> commands are executed, independent of the state of the client connection.<br/><br/>More on MULTI/EXEC:
<ul><li> <a href="http://code.google.com/p/redis/wiki/MultiExecCommand" target="_blank">http://code.google.com/p/redis/wiki/MultiExecCommand</a></li></ul>
<h3><a name="Blocking pop">Blocking pop</a></h3>
The commands BLPOP and BRPOP were added to support popping from a list in a blocking fashion. This means the client
connection will be blocked for a certain amount of time until another client pushes an item on a list. These commands
are frequently used in producer/consumer scenarios.<br/><br/>More on blocking pop:
<ul><li> <a href="http://code.google.com/p/redis/wiki/BlpopCommand" target="_blank">http://code.google.com/p/redis/wiki/BlpopCommand</a></li></ul>
<h3><a name="Publish/subscribe">Publish/subscribe</a></h3>
The family of publish/subscribe commands let clients publish messages onto channels and subscribe to receive all messages
that are published on channels. Also included are commands to receive all messages for which the channel matches a given pattern.<br/><br/>More on publish/subscribe:
<ul><li> <a href="http://code.google.com/p/redis/wiki/PublishSubscribe" target="_blank">http://code.google.com/p/redis/wiki/PublishSubscribe</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-3-publish-submit.html" target="_blank">http://antirez.com/post/redis-weekly-update-3-publish-submit.html</a></li><li> <a href="http://rediscookbook.org/pubsub_for_asynchronous_communication.html" target="_blank">http://rediscookbook.org/pubsub_for_asynchronous_communication.html</a></li></ul>
<h3><a name="Hashes">Hashes</a></h3>
This new datatype allows to store multiple key/value pairs on a single key. Together with the list of regular commands you
would expect for such a datatype (HSET, HGET, HDEL, HLEN, HKEYS, ...), it is also possible to use the values <i>inside</i> a hash
for any SORT operation.<br/><br/>More on hashes:
<ul><li> <a href="http://code.google.com/p/redis/wiki/HsetCommand" target="_blank">http://code.google.com/p/redis/wiki/HsetCommand</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-1.html" target="_blank">http://antirez.com/post/redis-weekly-update-1.html</a></li></ul>
<h3><a name="Virtual Memory">Virtual Memory</a></h3>
Redis Virtual Memory allows users to grow their dataset beyond the limits of their RAM.<br/><br/>More on virtual memory:
<ul><li> <a href="http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide" target="_blank">http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide</a></li><li> <a href="http://antirez.com/post/redis-virtual-memory-story.html" target="_blank">http://antirez.com/post/redis-virtual-memory-story.html</a></li></ul>
<h2><a name="Contributors">Contributors</a></h2><ul><li> Salvatore Sanfilippo</li><li> Pieter Noordhuis</li><li> Antonio Ognio</li><li> Alex McHale</li><li> Michel Martens</li><li> Damian Janowski</li><li> Bruno Deferrari</li><li> Ashley Martens</li><li> Derek Collison</li><li> Damian Janowski</li><li> Jeremy Zawodny</li><li> Konstantin Merenkov</li><li> Michel Martens</li><li> Sam Hendley</li></ul>
</div>
</div>
</div>
</body>
</html>
+45
View File
@@ -0,0 +1,45 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SetbitCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SETBIT _key_ _offset_ _value_ (Redis &gt;">SETBIT _key_ _offset_ _value_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">SetbitCommand</h1>
<div class="summary">
</div>
<div class="narrow">
<h1><a name="SETBIT _key_ _offset_ _value_ (Redis &gt;">SETBIT _key_ _offset_ _value_ (Redis &gt;</a></h1> 2.1.8) =
<i>Time complexity: O(1)</i><blockquote>Sets or clears the bit at <i>offset</i> in the string value stored at <i>key</i>.</blockquote>
The bit is either set or cleared depending on <i>value</i>, which can be either 0 or 1. When <i>key</i> does not exist, a new string value is created. The string is grown to make sure it can hold a bit at <i>offset</i>. The <i>offset</i> argument is required to be greater than or equal to 0, and is limited to 2<sup>32-1 (which limits bitmaps to 512MB).
When the string at <i>key</i> is grown, added bits are set to 0.<br/><br/><b>Warning</b>: When setting the last possible bit (<i>offset</i> equal to 2</sup>32-1) and the string value stored at <i>key</i> does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time.
On a 2010 Macbook Pro, setting bit number 2<sup>32-1 (512MB allocation) takes ~300ms,
setting bit number 2</sup>30-1 (128MB allocation) takes ~80ms,
setting bit number 2<sup>28-1 (32MB allocation) takes ~30ms and
setting bit number 2</sup>26-1 (8MB allocation) takes ~8ms.
Note that once this first allocation is done, subsequent calls to SETBIT for the same <i>key</i> will not have the allocation overhead.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the original bit value stored at <i>offset</i>.
</div>
</div>
</div>
</body>
</html>
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SetrangeCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SETRANGE _key_ _offset_ _value_ (Redis &gt;">SETRANGE _key_ _offset_ _value_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Examples">Examples</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Patterns">Patterns</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">SetrangeCommand</h1>
<div class="summary">
</div>
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="SETRANGE _key_ _offset_ _value_ (Redis &gt;">SETRANGE _key_ _offset_ _value_ (Redis &gt;</a></h1> 2.1.8) =
<i>Time complexity: O(1) not counting the time taken to copy the new string in place, as usually this string is small so the amoritzed time is O(1). Otheriwse O(M) with M being the length of the value argument</i><blockquote>Overwrites part of a string at <i>key</i> starting at the specified offset,for all the length of <i>value</i>.If the offset is over the old length of the string, the string is paddedwith zero bytes until needed. Non existing keys are considered likealready containing an empty string.</blockquote>
<h2><a name="Examples">Examples</a></h2>First example, basic usage setting a range.<br/><br/><pre class="codeblock python" name="code">
redis&gt; set foo &quot;Hello World&quot;
OK
redis&gt; setrange foo 6 &quot;Redis&quot;
(integer) 11
redis&gt; get foo
&quot;Hello Redis&quot;
</pre>Example of the zero padding behavior.<br/><br/><pre class="codeblock python python" name="code">
redis&gt; del foo
(integer) 1
redis&gt; setrange foo 10 bar
(integer) 13
redis&gt; get foo
&quot;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bar&quot;
</pre>Note that the maximum offset that you can set is 536870911 as Redis Strings are limited to 512 megabytes. You can still create longer arrays of values using multiple keys.<br/><br/><b>Warning</b>: When setting the last possible byte and the string value stored at <i>key</i> does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time.
On a 2010 Macbook Pro, setting byte number 536870911 (512MB allocation) takes ~300ms,
setting byte number 134217728 (128MB allocation) takes ~80ms,
setting bit number 33554432 (32MB allocation) takes ~30ms and
setting bit number 8388608 (8MB allocation) takes ~8ms.
Note that once this first allocation is done, subsequent calls to SETRANGE for the same <i>key</i> will not have the allocation overhead.<h2><a name="Patterns">Patterns</a></h2>Thanks to SETRANGE and the analogous GETRANGE command you can use Redis strings as a linear array of memory with O(1) random access. This is a very fast and efficient storage in many real world use cases.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the length of the string after it was modified by the command.
</div>
</div>
</div>
</body>
</html>
+1 -1
View File
@@ -26,7 +26,7 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;== String Commands ==<br/><br/><ul><li> <a href="SetCommand.html">SET</a></li><li> <a href="GetCommand.html">GET</a></li><li> <a href="GetsetCommand.html">GETSET</a></li><li> <a href="MgetCommand.html">MGET</a></li><li> <a href="SetnxCommand.html">SETNX</a></li><li> <a href="SetexCommand.html">SETEX</a></li><li> <a href="MsetCommand.html">MSET</a></li><li> <a href="MsetCommand.html">MSETNX</a></li><li> <a href="IncrCommand.html">INCR</a></li><li> <a href="IncrCommand.html">INCRBY</a></li><li> <a href="IncrCommand.html">DECR</a></li><li> <a href="IncrCommand.html">DECRBY</a></li><li> <a href="AppendCommand.html">APPEND</a></li><li> <a href="SubstrCommand.html">SUBSTR</a></li></ul>
&iuml;&raquo;&iquest;== String Commands ==<br/><br/><ul><li> <a href="SetCommand.html">SET</a></li><li> <a href="GetCommand.html">GET</a></li><li> <a href="StrlenCommand.html">STRLEN</a></li><li> <a href="GetsetCommand.html">GETSET</a></li><li> <a href="SetnxCommand.html">SETNX</a></li><li> <a href="SetexCommand.html">SETEX</a></li><li> <a href="SetbitCommand.html">SETBIT</a></li><li> <a href="GetbitCommand.html">GETBIT</a></li><li> <a href="MsetCommand.html">MSET</a></li><li> <a href="MsetCommand.html">MSETNX</a></li><li> <a href="MgetCommand.html">MGET</a></li><li> <a href="IncrCommand.html">INCR</a></li><li> <a href="IncrCommand.html">INCRBY</a></li><li> <a href="IncrCommand.html">DECR</a></li><li> <a href="IncrCommand.html">DECRBY</a></li><li> <a href="AppendCommand.html">APPEND</a></li><li> <a href="SetrangeCommand.html">SETRANGE</a></li><li> <a href="SubstrCommand.html">GETRANGE (or SUBSTR)</a></li></ul>
</div>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="Redis String Type">Redis String Type</a></h1>Strings are the most basic Redis kind of values. Redis Strings are binary safe, this means a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object, and so forth.<br/><br/>A String value can be at max 1 Gigabyte in length.<br/><br/>Strings are treated as integer values by the <a href="IncrCommand.html">INCR</a> commands family, in this respect the value of an intger is limited to a singed 64 bit value.<br/><br/>Note that the single elements contained in Redis <a href="Lists.html">Lists</a>, <a href="Sets.html">Sets</a> and <a href="SortedSets.html">Sorted Sets</a>, are Redis Strings.<h1><a name="Implementation details">Implementation details</a></h1>Strings are implemented using a dynamic strings library called <code name="code" class="python">sds.c</code> (simple dynamic strings). This library caches the current length of the string, so to obtain the length of a Redis string is an O(1) operation (but currently there is no such STRLEN command. It will likely be added later).<br/><br/>Redis strings are incapsualted into Redis Objects. Redis Objects use a reference counting memory management system, so a single Redis String can be shared in different places of the dataset. This means that if you happen to use the same strings many times (especially if you have <i>object sharing</i> turned on in the configuration file) Redis will try to use the same string object instead to allocate one new every time.<br/><br/>Starting from version 1.1 Redis is also able to encode in a special way strings that are actually just numbers. Instead to save the string as an array of characters Redis will save the integer value in order to use less memory. With many datasets this can reduce the memory usage of about 30% compared to Redis 1.0.
&iuml;&raquo;&iquest;#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="Redis String Type">Redis String Type</a></h1>Strings are the most basic Redis kind of values. Redis Strings are binary safe, this means a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object, and so forth.<br/><br/>A String value can be at max 512 Megabytes in length.<br/><br/>Strings are treated as integer values by the <a href="IncrCommand.html">INCR</a> commands family, in this respect the value of an intger is limited to a singed 64 bit value.<br/><br/>Note that the single elements contained in Redis <a href="Lists.html">Lists</a>, <a href="Sets.html">Sets</a> and <a href="SortedSets.html">Sorted Sets</a>, are Redis Strings.<h1><a name="Implementation details">Implementation details</a></h1>Strings are implemented using a dynamic strings library called <code name="code" class="python">sds.c</code> (simple dynamic strings). This library caches the current length of the string, so to obtain the length of a Redis string is an O(1) operation (but currently there is no such STRLEN command. It will likely be added later).<br/><br/>Redis strings are incapsualted into Redis Objects. Redis Objects use a reference counting memory management system, so a single Redis String can be shared in different places of the dataset. This means that if you happen to use the same strings many times (especially if you have <i>object sharing</i> turned on in the configuration file) Redis will try to use the same string object instead to allocate one new every time.<br/><br/>Starting from version 1.1 Redis is also able to encode in a special way strings that are actually just numbers. Instead to save the string as an array of characters Redis will save the integer value in order to use less memory. With many datasets this can reduce the memory usage of about 30% compared to Redis 1.0.
</div>
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div id="page">
<div id='header'>
<a href="index.html">
<img style="border:none" alt="Redis Documentation" src="redis.png">
</a>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>StrlenCommand: Contents</b><br>&nbsp;&nbsp;<a href="#STRLEN _key_ (Redis &gt;">STRLEN _key_ (Redis &gt;</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">StrlenCommand</h1>
<div class="summary">
</div>
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="STRLEN _key_ (Redis &gt;">STRLEN _key_ (Redis &gt;</a></h1> 2.1.8) =
<i>Time complexity: O(1)</i><blockquote>Returns the length of the string stored at the specified <i>key</i>.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the length of the string.
</div>
</div>
</div>
</body>
</html>
+3 -2
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>SubstrCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SUBSTR _key_ _start_ _end_">SUBSTR _key_ _start_ _end_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Examples">Examples</a>
<b>SubstrCommand: Contents</b><br>&nbsp;&nbsp;<a href="#SUBSTR _key_ _start_ _end_">SUBSTR _key_ _start_ _end_</a><br>&nbsp;&nbsp;<a href="#GETRANGE _key_ _start_ _end_">GETRANGE _key_ _start_ _end_</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Return value">Return value</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Examples">Examples</a>
</div>
<h1 class="wikiname">SubstrCommand</h1>
@@ -27,7 +27,8 @@
<div class="narrow">
&iuml;&raquo;&iquest;#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="SUBSTR _key_ _start_ _end_">SUBSTR _key_ _start_ _end_</a></h1>
<i>Time complexity: O(start+n) (with start being the start index and n the total length of the requested range). Note that the lookup part of this command is O(1) so for small strings this is actually an O(1) command.</i><blockquote>Return a subset of the string from offset <i>start</i> to offset <i>end</i>(both offsets are inclusive).Negative offsets can be used in order to provide an offset starting fromthe end of the string. So -1 means the last char, -2 the penultimate andso forth.</blockquote>
<h1><a name="GETRANGE _key_ _start_ _end_">GETRANGE _key_ _start_ _end_</a></h1>
<i>Time complexity: O(start+n) (with start being the start index and n the total length of the requested range). Note that the lookup part of this command is O(1) so for small strings this is actually an O(1) command.</i><b>Warning:</b> this command was renamed into GETRANGE. SUBSTR will be taken as an alias until the next major release of Redis.<br/><br/><blockquote>Return a subset of the string from offset <i>start</i> to offset <i>end</i>(both offsets are inclusive).Negative offsets can be used in order to provide an offset starting fromthe end of the string. So -1 means the last char, -2 the penultimate andso forth.</blockquote>
<blockquote>The function handles out of range requests without raising an error, butjust limiting the resulting range to the actual length of the string.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><h2><a name="Examples">Examples</a></h2><pre class="codeblock python" name="code">
redis&gt; set s &quot;This is a string&quot;
+1 -2
View File
@@ -39,7 +39,7 @@ vm-max-threads 4
</pre>This is the maximum number of threads used in order to perform I/O from/to the swap file. A good value is just to match the number of cores in your system.<br/><br/>However the special value of &quot;0&quot; will enable blocking VM. When VM is configured to be blocking it performs the I/O in a synchronous blocking way. This is what you can expect from blocking VM:
<ul><li> Clients accessing swapped out keys will block other clients while reading from disk, so the latency experimented by clients can be larger, especially if the disk is slow or busy and/or if there are big values swapped on disk.</li><li> The blocking VM performances are <b>overall</b> better, as there is no time lost in synchronization, spawning of threads, resuming blocked clients waiting for values.</li></ul>So if you are willing to accept an higher latency from time to time, blocking VM can be a good pick, especially if swapping happens rarely as most of your often accessed data happens to fit in your memory.<br/><br/>If instead you have a lot of swap in and swap out operations and you have many cores that you want to exploit, and in general when you don't want that clients dealing with swapped values will block other clients for a few milliseconds (or more if the swapped value is very big), then it's better to use threaded VM.<br/><br/>To experiment with your dataset and different configurations is warmly encouraged...<h1><a name="Random things to know">Random things to know</a></h1>
<h2><a name="A good place for the swap file">A good place for the swap file</a></h2>In many configurations the swap file can be fairly large, even 40GB or more.
Not all the kind of file systems are able to deal with large files in a good way, especially Mac OS X file system tends to be really lame about it.<br/><br/>The suggestion is to use Linux ext3 file system, or any other file system with good support for <b>sparse files</b>. What are sparse files?<br/><br/>Sparse files are files where a lot of the content happen to be empty. Advanced file systems like ext2, ext3, ext4, RaiserFS, Raiser4, and many others, are able to encode this files in a more efficient way and will allocate more space for the file when needed, that is, when more actual blocks of the file will be used.<br/><br/>The swap file is obviously pretty sparse, especially if the server is running since little time or it is much bigger compared to the amount of data swapped out. A file system not supporting sparse files can at some point block the Redis process while creating a very big file at once.<br/><br/>For a list of file systems supporting spare files <a href="http://en.wikipedia.org/wiki/Comparison_of_file_systems" target="_blank">check this Wikipedia page comparing different files systems</a>.<h2><a name="Monitoring the VM">Monitoring the VM</a></h2>Once you have a Redis system with VM enabled up and running, you may be very interested in knowing how it's working: how many objects are swapped in total, the number of objects swapped and loaded every second, and so forth.<br/><br/>There is an utility that is very handy in checking how the VM is working, that is part of <a href="http://code.google.com/p/redis-tools" target="_blank">Redis Tools</a>. This tool is called redis-stat, and using it is pretty straightforward:<br/><br/><pre class="codeblock python python python" name="code">
Not all the kind of file systems are able to deal with large files in a good way, especially Mac OS X file system tends to be really lame about it.<br/><br/>The suggestion is to use Linux ext3 file system, or any other file system with good support for <b>sparse files</b>. What are sparse files?<br/><br/>Sparse files are files where a lot of the content happen to be empty. Advanced file systems like ext2, ext3, ext4, RaiserFS, Raiser4, and many others, are able to encode this files in a more efficient way and will allocate more space for the file when needed, that is, when more actual blocks of the file will be used.<br/><br/>The swap file is obviously pretty sparse, especially if the server is running since little time or it is much bigger compared to the amount of data swapped out. A file system not supporting sparse files can at some point block the Redis process while creating a very big file at once.<br/><br/>For a list of file systems supporting spare files <a href="http://en.wikipedia.org/wiki/Comparison_of_file_systems" target="_blank">check this Wikipedia page comparing different files systems</a>.<h2><a name="Monitoring the VM">Monitoring the VM</a></h2>Once you have a Redis system with VM enabled up and running, you may be very interested in knowing how it's working: how many objects are swapped in total, the number of objects swapped and loaded every second, and so forth.<br/><br/>There is an utility that is very handy in checking how the VM is working, that is part of <a href="http://github.com/antirez/redis-tools" target="_blank">Redis Tools</a>. This tool is called redis-stat, and using it is pretty straightforward:<br/><br/><pre class="codeblock python python python" name="code">
$ ./redis-stat vmstat
--------------- objects --------------- ------ pages ------ ----- memory -----
load-in swap-out swapped delta used delta used delta
@@ -57,7 +57,6 @@ $ ./redis-stat vmstat
10087 18784 886771 -1574 894577 -1828 200.36M +91.60K
9330 19350 887411 +640 894817 +240 200.17M -189.72K
</pre>The above output is about a redis-server with VM enable, around 1 million of keys inside, and a lot of simulated load using the redis-load utility.<br/><br/>As you can see from the output a number of load-in and swap-out operations are happening every second. Note that the first line reports the actual values since the server was started, while the next lines are differences compared to the previous reading.<br/><br/>If you assigned enough memory to hold your working set of data, probably you should see a lot less dramatic swapping happening, so redis-stat can be a really valuable tool in order to understand if you need to shop for RAM ;)<h2><a name="Redis with VM enabled: better .rdb files or Append Only File?">Redis with VM enabled: better .rdb files or Append Only File?</a></h2>When VM is enabled, saving and loading the database are <b>much slower</b> operations. A DB that usually loads in 2 seconds takes 13 seconds with VM enabled if the server is configured to use the smallest memory possible (that is, vm-max-memory set to 0).<br/><br/>So you probably want to switch to a configuration using the Append Only File for persistence, so that you can perform the BGREWRITEAOF from time to time.<br/><br/>It is important to note that while a BGSAVE or BGREWRITEAOF is in progress Redis does <b>not</b> swap new values on disk. The VM will be read-only while there is another child accessing it. So if you have a lot of writes while there is a child working, the memory usage may grow.<h2><a name="Using as little memory as possible">Using as little memory as possible</a></h2>An interesting setup to turn Redis into an on-disk DB with just keys in memory is setting vm-max-memory to 0. If you don't mind some latency more and poorer performances but want to use very little memory for very big values, this is a good setup.<br/><br/>In this setup you should first try setting the VM as blocking (vm-max-threads 0) as with this configuration and high traffic the number of swap in and swap out operations will be huge, and threading will consume a lot of resources compared to a simple blocking implementation.<h2><a name="VM Stability">VM Stability</a></h2>VM is still experimental code, but in the latest weeks it was tested in many ways in development environments, and even in some production environment. No bugs were noticed during this testing period. Still the more obscure bugs may happen in non controlled environments where there are setups that we are not able to reproduce for some reason.<br/><br/>In this stage you are encouraged to try VM in your development environment, and even in production if your DB is not mission critical, but for instance just a big persistent cache of data that may go away without too much problems.<br/><br/>Please report any problem you will notice to the Redis Google Group or by IRC joining the #redis IRC channel on freenode.
</div>
</div>
+3 -2
View File
@@ -30,8 +30,9 @@
<h1><a name="ZRANK _key_ _member_ (Redis &gt;">ZRANK _key_ _member_ (Redis &gt;</a></h1> 1.3.4) =
<h1><a name="ZREVRANK _key_ _member_ (Redis &gt;">ZREVRANK _key_ _member_ (Redis &gt;</a></h1> 1.3.4) =
<i>Time complexity: O(log(N))</i><blockquote>ZRANK returns the rank of the member in the sorted set, with scores ordered from low to high. ZREVRANK returns the rank with scores ordered from high to low. When the given member does not exist in the sorted set, the special value 'nil' is returned. The returned rank (or index) of the member is 0-based for both commands.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
the rank (an integer number) represented as an string.
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a> or a nil <a href="ReplyTypes.html">bulk reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
the rank of the element as an integer reply if the element exists.
A nil bulk reply if there is no such element.
</pre>
</div>
+8 -6
View File
@@ -16,7 +16,7 @@
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
<b>index: Contents</b><br>&nbsp;&nbsp;<a href="#HOWTOs about selected features">HOWTOs about selected features</a><br>&nbsp;&nbsp;<a href="#Hacking">Hacking</a><br>&nbsp;&nbsp;<a href="#Videos">Videos</a>
<b>index: Contents</b><br>&nbsp;&nbsp;<a href="#Getting started">Getting started</a><br>&nbsp;&nbsp;<a href="#Full programming examples">Full programming examples</a><br>&nbsp;&nbsp;<a href="#FAQs and benchmarks">FAQs and benchmarks</a><br>&nbsp;&nbsp;<a href="#HOWTOs about selected features">HOWTOs about selected features</a><br>&nbsp;&nbsp;<a href="#Hacking">Hacking</a><br>&nbsp;&nbsp;<a href="#Videos">Videos</a><br>&nbsp;&nbsp;<a href="#Recipes and books">Recipes and books</a>
</div>
<h1 class="wikiname">index</h1>
@@ -26,12 +26,14 @@
</div>
<div class="narrow">
&iuml;&raquo;&iquest;= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python">&lt;-&gt;</code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
&iuml;&raquo;&iquest;= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<h1><a name="Getting started">Getting started</a></h1><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is a good starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="http://blog.mjrusso.com/2010/10/17/redis-from-the-ground-up.html" target="_blank">Redis from the ground up</a> is an impressive article to know more about Redis if you are a newcomer.</li><li> <a href="http://playnice.ly/blog/2010/10/19/getting-started-redis-python/" target="_blank">Getting started with Redis and Python</a></li></ul>
<h1><a name="Full programming examples">Full programming examples</a></h1><ul><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li></ul>
<h1><a name="FAQs and benchmarks">FAQs and benchmarks</a></h1><ul><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis. Not very up-to-date.</li></ul>
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python">&lt;-&gt;</code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li><li> <a href="RedisPipelining.html">Redis Pipelining Guide</a>.</li><li> Memory optimization: Full of keys, a <a href="http://antirez.com/post/redis-weekly-update-7.html" target="_blank">blog post at antirez.com</a> showing how to use Redis 2.0 and hashes to create a setup where you can store 5 times more data in your Redis instance.</li><li> <a href="http://antirez.com/post/autocomplete-with-redis.html" target="_blank">Implementing auto complete with Redis</a>.</li></ul>
<h1><a name="Hacking">Hacking</a></h1>
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li></ul>
<ul><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://confreaks.net/videos/62-mwrc2009-redis-key-value-nirvana" target="_blank">Ezra Zygmuntowicz at the Mountain West Ruby Conference 2009</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
<h1><a name="Recipes and books">Recipes and books</a></h1><ul><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe.</li><li> There is an ongoing effort to write a Redis book for O'Reilly</li></ul>
</div>
</div>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+53 -7
View File
@@ -21,6 +21,7 @@ daemonize no
pidfile /var/run/redis.pid
# Accept connections on the specified port, default is 6379.
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379
# If you want you can bind a single interface, if the bind option is not
@@ -50,6 +51,16 @@ loglevel verbose
# output for logging but daemonize, logs will be sent to /dev/null
logfile stdout
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no
# Specify the syslog identity.
# syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
@@ -281,8 +292,35 @@ appendfsync everysec
# "no" that is the safest pick from the point of view of durability.
no-appendfsync-on-rewrite no
################################## SLOW LOG ###################################
# The Redis Slow Log is a system to log queries that exceeded a specified
# execution time. The execution time does not include the I/O operations
# like talking with the client, sending the reply and so forth,
# but just the time needed to actually execute the command (this is the only
# stage of command execution where the thread is blocked and can not serve
# other requests in the meantime).
#
# You can configure the slow log with two parameters: one tells Redis
# what is the execution time, in microseconds, to exceed in order for the
# command to get logged, and the other parameter is the length of the
# slow log. When a new command is logged the oldest one is removed from the
# queue of logged commands.
# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
slowlog-log-slower-than 10000
# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 1024
################################ VIRTUAL MEMORY ###############################
### WARNING! Virtual Memory is deprecated in Redis 2.4
### The use of Virtual Memory is strongly discouraged.
# Virtual Memory allows Redis to work with datasets bigger than the actual
# amount of RAM needed to hold the whole dataset in memory.
# In order to do so very used keys are taken in memory while the other keys
@@ -355,17 +393,25 @@ vm-max-threads 4
############################### ADVANCED CONFIG ###############################
# Glue small output buffers together in order to send small replies in a
# single TCP packet. Uses a bit more CPU but most of the times it is a win
# in terms of number of queries per second. Use 'yes' if unsure.
glueoutputbuf yes
# Hashes are encoded in a special way (much more memory efficient) when they
# have at max a given numer of elements, and the biggest element does not
# exceed a given threshold. You can configure this limits with the following
# configuration directives.
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
hash-max-zipmap-entries 512
hash-max-zipmap-value 64
# Similarly to hashes, small lists are also encoded in a special way in order
# to save a lot of space. The special representation is only used when
# you are under the following limits:
list-max-ziplist-entries 512
list-max-ziplist-value 64
# Sets have a special encoding in just one case: when a set is composed
# of just strings that happens to be integers in radix 10 in the range
# of 64 bit signed integers.
# The following configuration setting sets the limit in the size of the
# set in order to use this special memory saving encoding.
set-max-intset-entries 512
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
+51 -30
View File
@@ -5,26 +5,40 @@
release_hdr := $(shell sh -c './mkreleasehdr.sh')
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
OPTIMIZATION?=-O2
CCCOLOR="\033[34m"
LINKCOLOR="\033[34;1m"
SRCCOLOR="\033[33m"
BINCOLOR="\033[37;1m"
MAKECOLOR="\033[32;1m"
ENDCOLOR="\033[0m"
ifndef V
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR);
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
endif
ifeq ($(uname_S),SunOS)
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
DEBUG?= -g -ggdb
else
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF)
CCLINK?= -lm -pthread
DEBUG?= -g -rdynamic -ggdb
endif
ifeq ($(USE_TCMALLOC),yes)
CCLINK+= -ltcmalloc
CFLAGS+= -DUSE_TCMALLOC
endif
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb
CCOPT= $(CFLAGS) $(ARCH) $(PROF)
PREFIX= /usr/local
INSTALL_BIN= $(PREFIX)/bin
INSTALL= cp -p
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o syncio.o
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o syncio.o slowlog.o
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
@@ -37,6 +51,9 @@ CHECKDUMPPRGNAME = redis-check-dump
CHECKAOFPRGNAME = redis-check-aof
all: redis-benchmark redis-cli redis-check-dump redis-check-aof redis-server
@echo ""
@echo "Hint: To run 'make test' is a good idea ;)"
@echo ""
# Deps (use make dep to generate this)
adlist.o: adlist.c adlist.h zmalloc.h
@@ -47,7 +64,6 @@ ae_select.o: ae_select.c
anet.o: anet.c fmacros.h anet.h
aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
chprgname.o: chprgname.c
config.o: config.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
@@ -69,21 +85,26 @@ pubsub.o: pubsub.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
rdb.o: rdb.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h lzf.h
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
zmalloc.h
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \
../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
redis-check-dump.o: redis-check-dump.c lzf.h
redis-cli.o: redis-cli.c fmacros.h version.h sds.h adlist.h zmalloc.h
redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \
sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h
redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h slowlog.h
release.o: release.c release.h
replication.o: replication.c redis.h fmacros.h config.h ae.h sds.h dict.h \
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
sds.o: sds.c sds.h zmalloc.h
sha1.o: sha1.c sha1.h
slowlog.o: slowlog.c redis.h fmacros.h config.h ae.h sds.h dict.h \
adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h \
slowlog.h
sort.o: sort.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h pqsort.h
syncio.o: syncio.c
syncio.o: syncio.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
t_hash.o: t_hash.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
t_list.o: t_list.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
@@ -100,47 +121,47 @@ vm.o: vm.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
ziplist.o: ziplist.c zmalloc.h ziplist.h
zipmap.o: zipmap.c zmalloc.h
zmalloc.o: zmalloc.c config.h
zmalloc.o: zmalloc.c config.h zmalloc.h
dependencies:
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)hiredis$(ENDCOLOR)
cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)linenoise$(ENDCOLOR)
cd ../deps/linenoise && $(MAKE) ARCH="$(ARCH)"
redis-server: $(OBJ)
$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
@echo ""
@echo "Hint: To run 'make test' is a good idea ;)"
@echo ""
$(QUIET_LINK)$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ) $(CCLINK)
redis-benchmark: $(BENCHOBJ)
cd ../deps/hiredis && $(MAKE) static
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a
redis-benchmark: dependencies $(BENCHOBJ)
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)hiredis$(ENDCOLOR)
cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
$(QUIET_LINK)$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a $(CCLINK)
redis-benchmark.o:
$(CC) -c $(CFLAGS) -I../deps/hiredis $(DEBUG) $(COMPILE_TIME) $<
$(QUIET_CC)$(CC) -c $(CFLAGS) -I../deps/hiredis $(DEBUG) $(COMPILE_TIME) $<
redis-cli: $(CLIOBJ)
cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
cd ../deps/linenoise && $(MAKE) ARCH="$(ARCH)"
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o
redis-cli: dependencies $(CLIOBJ)
$(QUIET_LINK)$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(CCLINK)
redis-cli.o:
$(CC) -c $(CFLAGS) -I../deps/hiredis -I../deps/linenoise $(DEBUG) $(COMPILE_TIME) $<
$(QUIET_CC)$(CC) -c $(CFLAGS) -I../deps/hiredis -I../deps/linenoise $(DEBUG) $(COMPILE_TIME) $<
redis-check-dump: $(CHECKDUMPOBJ)
$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ)
$(QUIET_LINK)$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ) $(CCLINK)
redis-check-aof: $(CHECKAOFOBJ)
$(CC) -o $(CHECKAOFPRGNAME) $(CCOPT) $(DEBUG) $(CHECKAOFOBJ)
$(QUIET_LINK)$(CC) -o $(CHECKAOFPRGNAME) $(CCOPT) $(DEBUG) $(CHECKAOFOBJ) $(CCLINK)
.c.o:
$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
$(QUIET_CC)$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
clean:
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov
cd ../deps/hiredis && $(MAKE) clean
cd ../deps/linenoise && $(MAKE) clean
dep:
$(CC) -MM *.c
$(CC) -MM *.c -I ../deps/hiredis -I ../deps/linenoise
test:
test: redis-server
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
bench:
+15 -15
View File
@@ -64,11 +64,11 @@ int anetNonBlock(char *err, int fd)
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) {
anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno));
return ANET_ERR;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
@@ -79,7 +79,7 @@ int anetTcpNoDelay(char *err, int fd)
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
{
anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
anetSetError(err, "setsockopt TCP_NODELAY: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
@@ -89,7 +89,7 @@ int anetSetSendBuffer(char *err, int fd, int buffsize)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
{
anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
@@ -99,7 +99,7 @@ int anetTcpKeepAlive(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
@@ -115,7 +115,7 @@ int anetResolve(char *err, char *host, char *ipbuf)
he = gethostbyname(host);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", host);
anetSetError(err, "can't resolve: %s", host);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
@@ -127,14 +127,14 @@ int anetResolve(char *err, char *host, char *ipbuf)
static int anetCreateSocket(char *err, int domain) {
int s, on = 1;
if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "creating socket: %s\n", strerror(errno));
anetSetError(err, "creating socket: %s", strerror(errno));
return ANET_ERR;
}
/* Make sure connection-intensive things like the redis benckmark
* will be able to close/open sockets a zillion of times */
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
return ANET_ERR;
}
return s;
@@ -157,7 +157,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
he = gethostbyname(addr);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", addr);
anetSetError(err, "can't resolve: %s", addr);
close(s);
return ANET_ERR;
}
@@ -172,7 +172,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
flags & ANET_CONNECT_NONBLOCK)
return s;
anetSetError(err, "connect: %s\n", strerror(errno));
anetSetError(err, "connect: %s", strerror(errno));
close(s);
return ANET_ERR;
}
@@ -208,7 +208,7 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
flags & ANET_CONNECT_NONBLOCK)
return s;
anetSetError(err, "connect: %s\n", strerror(errno));
anetSetError(err, "connect: %s", strerror(errno));
close(s);
return ANET_ERR;
}
@@ -257,12 +257,12 @@ int anetWrite(int fd, char *buf, int count)
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
if (bind(s,sa,len) == -1) {
anetSetError(err, "bind: %s\n", strerror(errno));
anetSetError(err, "bind: %s", strerror(errno));
close(s);
return ANET_ERR;
}
if (listen(s, 511) == -1) { /* the magic 511 constant is from nginx */
anetSetError(err, "listen: %s\n", strerror(errno));
anetSetError(err, "listen: %s", strerror(errno));
close(s);
return ANET_ERR;
}
@@ -282,7 +282,7 @@ int anetTcpServer(char *err, int port, char *bindaddr)
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bindaddr && inet_aton(bindaddr, &sa.sin_addr) == 0) {
anetSetError(err, "Invalid bind address\n");
anetSetError(err, "invalid bind address");
close(s);
return ANET_ERR;
}
@@ -315,7 +315,7 @@ static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *l
if (errno == EINTR)
continue;
else {
anetSetError(err, "accept: %s\n", strerror(errno));
anetSetError(err, "accept: %s", strerror(errno));
return ANET_ERR;
}
}
+4
View File
@@ -35,6 +35,10 @@
#define ANET_ERR -1
#define ANET_ERR_LEN 256
#if defined(__sun)
#define AF_LOCAL AF_UNIX
#endif
int anetTcpConnect(char *err, char *addr, int port);
int anetTcpNonBlockConnect(char *err, char *addr, int port);
int anetUnixConnect(char *err, char *path);
+19 -8
View File
@@ -19,15 +19,15 @@ void stopAppendOnly(void) {
server.appendseldb = -1;
server.appendonly = 0;
/* rewrite operation in progress? kill it, wait child exit */
if (server.bgsavechildpid != -1) {
if (server.bgrewritechildpid != -1) {
int statloc;
if (kill(server.bgsavechildpid,SIGKILL) != -1)
if (kill(server.bgrewritechildpid,SIGKILL) != -1)
wait3(&statloc,0,NULL);
/* reset the buffer accumulating changes while the child saves */
sdsfree(server.bgrewritebuf);
server.bgrewritebuf = sdsempty();
server.bgsavechildpid = -1;
server.bgrewritechildpid = -1;
}
}
@@ -220,8 +220,10 @@ int loadAppendOnlyFile(char *filename) {
int appendonly = server.appendonly;
long loops = 0;
if (redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0)
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
fclose(fp);
return REDIS_ERR;
}
if (fp == NULL) {
redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
@@ -282,10 +284,14 @@ int loadAppendOnlyFile(char *filename) {
/* The fake client should not have a reply */
redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
/* The fake client should never get blocked */
redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0);
/* Clean up, ready for the next command */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
zfree(argv);
/* Clean up. Command code may have changed argv/argc so we use the
* argv/argc of the client instead of the local variables. */
for (j = 0; j < fakeClient->argc; j++)
decrRefCount(fakeClient->argv[j]);
zfree(fakeClient->argv);
/* Handle swapping while loading big datasets when VM is on */
force_swapout = 0;
@@ -344,7 +350,7 @@ int rewriteAppendOnlyFile(char *filename) {
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetIterator(d);
di = dictGetSafeIterator(d);
if (!di) {
fclose(fp);
return REDIS_ERR;
@@ -651,6 +657,11 @@ void backgroundRewriteDoneHandler(int statloc) {
if (server.appendfsync != APPENDFSYNC_NO) aof_fsync(fd);
server.appendseldb = -1; /* Make sure it will issue SELECT */
redisLog(REDIS_NOTICE,"The new append only file was selected for future appends.");
/* Clear regular AOF buffer since its contents was just written to
* the new AOF from the background rewrite buffer. */
sdsfree(server.aofbuf);
server.aofbuf = sdsempty();
} else {
/* If append only is disabled we just generate a dump in this
* format. Why not? */
+131 -5
View File
@@ -30,6 +30,7 @@ void loadServerConfig(char *filename) {
char buf[REDIS_CONFIGLINE_MAX+1], *err = NULL;
int linenum = 0;
sds line = NULL;
int really_use_vm = 0;
if (filename[0] == '-' && filename[1] == '\0')
fp = stdin;
@@ -66,7 +67,7 @@ void loadServerConfig(char *filename) {
}
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
server.port = atoi(argv[1]);
if (server.port < 1 || server.port > 65535) {
if (server.port < 0 || server.port > 65535) {
err = "Invalid port"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"bind") && argc == 2) {
@@ -114,6 +115,42 @@ void loadServerConfig(char *filename) {
}
fclose(logfp);
}
} else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"syslog-ident") && argc == 2) {
if (server.syslog_ident) zfree(server.syslog_ident);
server.syslog_ident = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
struct {
const char *name;
const int value;
} validSyslogFacilities[] = {
{"user", LOG_USER},
{"local0", LOG_LOCAL0},
{"local1", LOG_LOCAL1},
{"local2", LOG_LOCAL2},
{"local3", LOG_LOCAL3},
{"local4", LOG_LOCAL4},
{"local5", LOG_LOCAL5},
{"local6", LOG_LOCAL6},
{"local7", LOG_LOCAL7},
{NULL, 0}
};
int i;
for (i = 0; validSyslogFacilities[i].name; i++) {
if (!strcasecmp(validSyslogFacilities[i].name, argv[1])) {
server.syslog_facility = validSyslogFacilities[i].value;
break;
}
}
if (!validSyslogFacilities[i].name) {
err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"databases") && argc == 2) {
server.dbnum = atoi(argv[1]);
if (server.dbnum < 1) {
@@ -158,10 +195,8 @@ void loadServerConfig(char *filename) {
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"glueoutputbuf") && argc == 2) {
if ((server.glueoutputbuf = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"glueoutputbuf")) {
redisLog(REDIS_WARNING, "Deprecated configuration directive: \"%s\"", argv[0]);
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
if ((server.rdbcompression = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
@@ -209,6 +244,10 @@ void loadServerConfig(char *filename) {
if ((server.vm_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"really-use-vm") && argc == 2) {
if ((really_use_vm = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"vm-swap-file") && argc == 2) {
zfree(server.vm_swap_file);
server.vm_swap_file = zstrdup(argv[1]);
@@ -254,6 +293,12 @@ void loadServerConfig(char *filename) {
err = "Target command name already exists"; goto loaderr;
}
}
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
argc == 2)
{
server.slowlog_log_slower_than = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
server.slowlog_max_len = strtoll(argv[1],NULL,10);
} else {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
@@ -263,6 +308,7 @@ void loadServerConfig(char *filename) {
sdsfree(line);
}
if (fp != stdin) fclose(fp);
if (server.vm_enabled && !really_use_vm) goto vm_warning;
return;
loaderr:
@@ -271,6 +317,15 @@ loaderr:
fprintf(stderr, ">>> '%s'\n", line);
fprintf(stderr, "%s\n", err);
exit(1);
vm_warning:
fprintf(stderr, "\nARE YOU SURE YOU WANT TO USE VM?\n\n");
fprintf(stderr, "Redis Virtual Memory is going to be deprecated soon,\n");
fprintf(stderr, "we think you should NOT use it, but use Redis only if\n");
fprintf(stderr, "your data is suitable for an in-memory database.\n");
fprintf(stderr, "If you *really* want VM add this in the config file:\n");
fprintf(stderr, "\n really-use-vm yes\n\n");
exit(1);
}
/*-----------------------------------------------------------------------------
@@ -392,6 +447,32 @@ void configSetCommand(redisClient *c) {
if (yn == -1) goto badfmt;
server.repl_serve_stale_data = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"dir")) {
if (chdir((char*)o->ptr) == -1) {
addReplyErrorFormat(c,"Changing directory: %s", strerror(errno));
return;
}
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-zipmap-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_zipmap_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-zipmap-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_zipmap_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.set_max_intset_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-log-slower-than")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR) goto badfmt;
server.slowlog_log_slower_than = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-max-len")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.slowlog_max_len = (unsigned)ll;
} else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr);
@@ -414,6 +495,16 @@ void configGetCommand(redisClient *c) {
int matches = 0;
redisAssert(o->encoding == REDIS_ENCODING_RAW);
if (stringmatch(pattern,"dir",0)) {
char buf[1024];
if (getcwd(buf,sizeof(buf)) == NULL)
buf[0] = '\0';
addReplyBulkCString(c,"dir");
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"dbfilename",0)) {
addReplyBulkCString(c,"dbfilename");
addReplyBulkCString(c,server.dbfilename);
@@ -507,6 +598,41 @@ void configGetCommand(redisClient *c) {
addReplyBulkCString(c,server.repl_serve_stale_data ? "yes" : "no");
matches++;
}
if (stringmatch(pattern,"hash-max-zipmap-entries",0)) {
addReplyBulkCString(c,"hash-max-zipmap-entries");
addReplyBulkLongLong(c,server.hash_max_zipmap_entries);
matches++;
}
if (stringmatch(pattern,"hash-max-zipmap-value",0)) {
addReplyBulkCString(c,"hash-max-zipmap-value");
addReplyBulkLongLong(c,server.hash_max_zipmap_value);
matches++;
}
if (stringmatch(pattern,"list-max-ziplist-entries",0)) {
addReplyBulkCString(c,"list-max-ziplist-entries");
addReplyBulkLongLong(c,server.list_max_ziplist_entries);
matches++;
}
if (stringmatch(pattern,"list-max-ziplist-value",0)) {
addReplyBulkCString(c,"list-max-ziplist-value");
addReplyBulkLongLong(c,server.list_max_ziplist_value);
matches++;
}
if (stringmatch(pattern,"set-max-intset-entries",0)) {
addReplyBulkCString(c,"set-max-intset-entries");
addReplyBulkLongLong(c,server.set_max_intset_entries);
matches++;
}
if (stringmatch(pattern,"slowlog-log-slower-than",0)) {
addReplyBulkCString(c,"slowlog-log-slower-than");
addReplyBulkLongLong(c,server.slowlog_log_slower_than);
matches++;
}
if (stringmatch(pattern,"slowlog-max-len",0)) {
addReplyBulkCString(c,"slowlog-max-len");
addReplyBulkLongLong(c,server.slowlog_max_len);
matches++;
}
setDeferredMultiBulkLength(c,replylen,matches*2);
}
+29 -6
View File
@@ -180,7 +180,13 @@ void flushallCommand(redisClient *c) {
kill(server.bgsavechildpid,SIGKILL);
rdbRemoveTempFile(server.bgsavechildpid);
}
rdbSave(server.dbfilename);
if (server.saveparamslen > 0) {
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
int saved_dirty = server.dirty;
rdbSave(server.dbfilename);
server.dirty = saved_dirty;
}
server.dirty++;
}
@@ -453,6 +459,11 @@ void propagateExpire(redisDb *db, robj *key) {
int expireIfNeeded(redisDb *db, robj *key) {
time_t when = getExpire(db,key);
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
@@ -464,8 +475,6 @@ int expireIfNeeded(redisDb *db, robj *key) {
return time(NULL) > when;
}
if (when < 0) return 0;
/* Return when this key has not expired */
if (time(NULL) <= when) return 0;
@@ -492,10 +501,24 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
addReply(c,shared.czero);
return;
}
if (seconds <= 0) {
if (dbDelete(c->db,key)) server.dirty++;
addReply(c, shared.cone);
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
* should never be executed as a DEL when load the AOF or in the context
* of a slave instance.
*
* Instead we take the other branch of the IF statement setting an expire
* (possibly in the past) and wait for an explicit DEL from the master. */
if (seconds <= 0 && !server.loading && !server.masterhost) {
robj *aux;
redisAssert(dbDelete(c->db,key));
server.dirty++;
/* Replicate/AOF this as an explicit DEL. */
aux = createStringObject("DEL",3);
rewriteClientCommandVector(c,2,aux,key);
decrRefCount(aux);
touchWatchedKey(c->db,key);
addReply(c, shared.cone);
return;
} else {
time_t when = time(NULL)+seconds;
+15 -4
View File
@@ -80,7 +80,7 @@ void computeDatasetDigest(unsigned char *final) {
redisDb *db = server.db+j;
if (dictSize(db->dict) == 0) continue;
di = dictGetIterator(db->dict);
di = dictGetSafeIterator(db->dict);
/* hash the DB id, so the same dataset moved in a different
* DB will lead to a different digest */
@@ -101,6 +101,11 @@ void computeDatasetDigest(unsigned char *final) {
/* Make sure the key is loaded if VM is active */
o = lookupKeyRead(db,keyobj);
if (o == NULL) {
/* Key expired on lookup? Try the next one. */
decrRefCount(keyobj);
continue;
}
aux = htonl(o->type);
mixDigest(digest,&aux,sizeof(aux));
@@ -121,7 +126,7 @@ void computeDatasetDigest(unsigned char *final) {
} else if (o->type == REDIS_SET) {
setTypeIterator *si = setTypeInitIterator(o);
robj *ele;
while((ele = setTypeNext(si)) != NULL) {
while((ele = setTypeNextObject(si)) != NULL) {
xorObjectDigest(digest,ele);
decrRefCount(ele);
}
@@ -152,10 +157,10 @@ void computeDatasetDigest(unsigned char *final) {
unsigned char eledigest[20];
memset(eledigest,0,20);
obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY);
mixObjectDigest(eledigest,obj);
decrRefCount(obj);
obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE);
mixObjectDigest(eledigest,obj);
decrRefCount(obj);
xorDigest(digest,eledigest,20);
@@ -284,6 +289,12 @@ void debugCommand(redisClient *c) {
d = sdscatprintf(d, "%02x",digest[j]);
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;
usleep(utime);
addReply(c,shared.ok);
} else {
addReplyError(c,
"Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
+15 -5
View File
@@ -244,9 +244,9 @@ int dictRehashMilliseconds(dict *d, int ms) {
}
/* This function performs just a step of rehashing, and only if there are
* not iterators bound to our hash table. When we have iterators in the middle
* of a rehashing we can't mess with the two hash tables otherwise some element
* can be missed or duplicated.
* no safe iterators bound to our hash table. When we have iterators in the
* middle of a rehashing we can't mess with the two hash tables otherwise
* some element can be missed or duplicated.
*
* This function is called by common lookup or update operations in the
* dictionary so that the hash table automatically migrates from H1 to H2
@@ -423,17 +423,26 @@ dictIterator *dictGetIterator(dict *d)
iter->d = d;
iter->table = 0;
iter->index = -1;
iter->safe = 0;
iter->entry = NULL;
iter->nextEntry = NULL;
return iter;
}
dictIterator *dictGetSafeIterator(dict *d) {
dictIterator *i = dictGetIterator(d);
i->safe = 1;
return i;
}
dictEntry *dictNext(dictIterator *iter)
{
while (1) {
if (iter->entry == NULL) {
dictht *ht = &iter->d->ht[iter->table];
if (iter->index == -1 && iter->table == 0) iter->d->iterators++;
if (iter->safe && iter->index == -1 && iter->table == 0)
iter->d->iterators++;
iter->index++;
if (iter->index >= (signed) ht->size) {
if (dictIsRehashing(iter->d) && iter->table == 0) {
@@ -460,7 +469,8 @@ dictEntry *dictNext(dictIterator *iter)
void dictReleaseIterator(dictIterator *iter)
{
if (!(iter->index == -1 && iter->table == 0)) iter->d->iterators--;
if (iter->safe && !(iter->index == -1 && iter->table == 0))
iter->d->iterators--;
zfree(iter);
}
+6 -2
View File
@@ -74,10 +74,13 @@ typedef struct dict {
int iterators; /* number of iterators currently running */
} dict;
/* If safe is set to 1 this is a safe iteartor, that means, you can call
* dictAdd, dictFind, and other functions against the dictionary even while
* iterating. Otherwise it is a non safe iterator, and only dictNext()
* should be called while iterating. */
typedef struct dictIterator {
dict *d;
int table;
int index;
int table, index, safe;
dictEntry *entry, *nextEntry;
} dictIterator;
@@ -132,6 +135,7 @@ dictEntry * dictFind(dict *d, const void *key);
void *dictFetchValue(dict *d, const void *key);
int dictResize(dict *d);
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
+1 -1
View File
@@ -3,7 +3,7 @@
#define _BSD_SOURCE
#ifdef __linux__
#if defined(__linux__) || defined(__OpenBSD__)
#define _XOPEN_SOURCE 700
#else
#define _XOPEN_SOURCE
+42 -12
View File
@@ -1,4 +1,4 @@
/* Automatically generated by utils/generate-command-help.rb, do not edit. */
/* Automatically generated by generate-command-help.rb, do not edit. */
#ifndef __REDIS_HELP_H
#define __REDIS_HELP_H
@@ -53,11 +53,21 @@ struct commandHelp {
"Remove and get the last element in a list, or block until one is available",
2,
"1.3.1" },
{ "BRPOPLPUSH",
"source destination timeout",
"Pop a value from a list, push it to another list and return it; or block until one is available",
2,
"2.1.7" },
{ "CONFIG GET",
"parameter",
"Get the value of a configuration parameter",
9,
"2.0" },
{ "CONFIG RESETSTAT",
"-",
"Reset the stats returned by INFO",
9,
"2.0" },
{ "CONFIG SET",
"parameter value",
"Set a configuration parameter to the given value",
@@ -79,7 +89,7 @@ struct commandHelp {
9,
"0.101" },
{ "DECR",
"key decrement",
"key",
"Decrement the integer value of a key by one",
1,
"0.07" },
@@ -138,6 +148,11 @@ struct commandHelp {
"Get the value of a key",
1,
"0.07" },
{ "GETBIT",
"key offset",
"Returns the bit value at offset in the string value stored at key",
1,
"2.1.8" },
{ "GETSET",
"key value",
"Set the string value of a key and return its old value",
@@ -344,12 +359,12 @@ struct commandHelp {
0,
"0.07" },
{ "RENAME",
"old new",
"key newkey",
"Rename a key",
0,
"0.07" },
{ "RENAMENX",
"old new",
"key newkey",
"Rename a key, only if the new key does not exist",
0,
"0.07" },
@@ -408,8 +423,13 @@ struct commandHelp {
"Set the string value of a key",
1,
"0.07" },
{ "SETBIT",
"key offset value",
"Sets or clears the bit at offset in the string value stored at key",
1,
"2.1.8" },
{ "SETEX",
"key timestamp value",
"key seconds value",
"Set the value and expiration of a key",
1,
"1.3.10" },
@@ -418,6 +438,11 @@ struct commandHelp {
"Set the value of a key, only if the key does not exist",
1,
"0.07" },
{ "SETRANGE",
"key offset value",
"Overwrite part of a string at key starting at the specified offset",
1,
"2.1.8" },
{ "SHUTDOWN",
"-",
"Synchronously save the dataset to disk and then shut down the server",
@@ -454,7 +479,7 @@ struct commandHelp {
3,
"0.091" },
{ "SORT",
"key [BY pattern] [LIMIT start count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]",
"key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]",
"Sort the elements in a list, set or sorted set",
0,
"0.07" },
@@ -484,7 +509,7 @@ struct commandHelp {
6,
"1.3.8" },
{ "SUBSTR",
"key start stop",
"key start end",
"Get a substring of the string stored at a key",
1,
"1.3.4" },
@@ -549,17 +574,17 @@ struct commandHelp {
4,
"1.1" },
{ "ZINTERSTORE",
"destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
"destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
"Intersect multiple sorted sets and store the resulting sorted set in a new key",
4,
"1.3.10" },
{ "ZRANGE",
"key start stop",
"key start stop [WITHSCORES]",
"Return a range of members in a sorted set, by index",
4,
"1.1" },
{ "ZRANGEBYSCORE",
"key min max",
"key min max [WITHSCORES] [LIMIT offset count]",
"Return a range of members in a sorted set, by score",
4,
"1.050" },
@@ -584,10 +609,15 @@ struct commandHelp {
4,
"1.1" },
{ "ZREVRANGE",
"key start stop",
"key start stop [WITHSCORES]",
"Return a range of members in a sorted set, by index, with scores ordered from high to low",
4,
"1.1" },
{ "ZREVRANGEBYSCORE",
"key max min [WITHSCORES] [LIMIT offset count]",
"Return a range of members in a sorted set, by score, with scores ordered from high to low",
4,
"2.1.6" },
{ "ZREVRANK",
"key member",
"Determine the index of a member in a sorted set, with scores ordered from high to low",
@@ -599,7 +629,7 @@ struct commandHelp {
4,
"1.1" },
{ "ZUNIONSTORE",
"destination key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
"destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
"Add multiple sorted sets and store the resulting sorted set in a new key",
4,
"1.3.10" }
+1 -1
View File
@@ -179,7 +179,7 @@ intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
}
/* Delete integer from intset */
intset *intsetRemove(intset *is, int64_t value, uint8_t *success) {
intset *intsetRemove(intset *is, int64_t value, int *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 0;
+1 -1
View File
@@ -10,7 +10,7 @@ typedef struct intset {
intset *intsetNew(void);
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
intset *intsetRemove(intset *is, int64_t value, uint8_t *success);
intset *intsetRemove(intset *is, int64_t value, int *success);
uint8_t intsetFind(intset *is, int64_t value);
int64_t intsetRandom(intset *is);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
+12 -3
View File
@@ -24,14 +24,14 @@ void freeClientMultiState(redisClient *c) {
}
/* Add a new command into the MULTI commands queue */
void queueMultiCommand(redisClient *c, struct redisCommand *cmd) {
void queueMultiCommand(redisClient *c) {
multiCmd *mc;
int j;
c->mstate.commands = zrealloc(c->mstate.commands,
sizeof(multiCmd)*(c->mstate.count+1));
mc = c->mstate.commands+c->mstate.count;
mc->cmd = cmd;
mc->cmd = c->cmd;
mc->argc = c->argc;
mc->argv = zmalloc(sizeof(robj*)*c->argc);
memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);
@@ -78,6 +78,7 @@ void execCommand(redisClient *c) {
int j;
robj **orig_argv;
int orig_argc;
struct redisCommand *orig_cmd;
if (!(c->flags & REDIS_MULTI)) {
addReplyError(c,"EXEC without MULTI");
@@ -105,14 +106,22 @@ void execCommand(redisClient *c) {
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
orig_argv = c->argv;
orig_argc = c->argc;
orig_cmd = c->cmd;
addReplyMultiBulkLen(c,c->mstate.count);
for (j = 0; j < c->mstate.count; j++) {
c->argc = c->mstate.commands[j].argc;
c->argv = c->mstate.commands[j].argv;
call(c,c->mstate.commands[j].cmd);
c->cmd = c->mstate.commands[j].cmd;
call(c);
/* Commands may alter argc/argv, restore mstate. */
c->mstate.commands[j].argc = c->argc;
c->mstate.commands[j].argv = c->argv;
c->mstate.commands[j].cmd = c->cmd;
}
c->argv = orig_argv;
c->argc = orig_argc;
c->cmd = orig_cmd;
freeClientMultiState(c);
initClientMultiState(c);
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS);
+120 -106
View File
@@ -31,6 +31,7 @@ redisClient *createClient(int fd) {
c->reqtype = 0;
c->argc = 0;
c->argv = NULL;
c->cmd = NULL;
c->multibulklen = 0;
c->bulklen = -1;
c->sentlen = 0;
@@ -41,8 +42,10 @@ redisClient *createClient(int fd) {
c->reply = listCreate();
listSetFreeMethod(c->reply,decrRefCount);
listSetDupMethod(c->reply,dupClientReplyValue);
c->blocking_keys = NULL;
c->blocking_keys_num = 0;
c->bpop.keys = NULL;
c->bpop.count = 0;
c->bpop.timeout = 0;
c->bpop.target = NULL;
c->io_keys = listCreate();
c->watched_keys = listCreate();
listSetFreeMethod(c->io_keys,decrRefCount);
@@ -58,9 +61,6 @@ redisClient *createClient(int fd) {
/* Set the event loop to listen for write events on the client's socket.
* Typically gets called every time a reply is built. */
int _installWriteEvent(redisClient *c) {
/* When CLOSE_AFTER_REPLY is set, no more replies may be added! */
redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY));
if (c->fd <= 0) return REDIS_ERR;
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||
@@ -86,9 +86,15 @@ robj *dupLastObjectIfNeeded(list *reply) {
return listNodeValue(ln);
}
/* -----------------------------------------------------------------------------
* Low level functions to add more data to output buffers.
* -------------------------------------------------------------------------- */
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
size_t available = sizeof(c->buf)-c->bufpos;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;
/* If there already are entries in the reply list, we cannot
* add anything more to the static buffer. */
if (listLength(c->reply) > 0) return REDIS_ERR;
@@ -103,6 +109,9 @@ int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
void _addReplyObjectToList(redisClient *c, robj *o) {
robj *tail;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
if (listLength(c->reply) == 0) {
incrRefCount(o);
listAddNodeTail(c->reply,o);
@@ -126,6 +135,12 @@ void _addReplyObjectToList(redisClient *c, robj *o) {
* needed it will be free'd, otherwise it ends up in a robj. */
void _addReplySdsToList(redisClient *c, sds s) {
robj *tail;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) {
sdsfree(s);
return;
}
if (listLength(c->reply) == 0) {
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
} else {
@@ -146,6 +161,9 @@ void _addReplySdsToList(redisClient *c, sds s) {
void _addReplyStringToList(redisClient *c, char *s, size_t len) {
robj *tail;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
if (listLength(c->reply) == 0) {
listAddNodeTail(c->reply,createStringObject(s,len));
} else {
@@ -163,6 +181,11 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) {
}
}
/* -----------------------------------------------------------------------------
* Higher level functions to queue data on the client output buffer.
* The following functions are the ones that commands implementations will call.
* -------------------------------------------------------------------------- */
void addReply(redisClient *c, robj *obj) {
if (_installWriteEvent(c) != REDIS_OK) return;
redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
@@ -178,6 +201,9 @@ void addReply(redisClient *c, robj *obj) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
} else {
/* FIXME: convert the long into string and use _addReplyToBuffer()
* instead of calling getDecodedObject. As this place in the
* code is too performance critical. */
obj = getDecodedObject(obj);
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
@@ -275,6 +301,7 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
}
}
/* Add a duble as a bulk reply */
void addReplyDouble(redisClient *c, double d) {
char dbuf[128], sbuf[128];
int dlen, slen;
@@ -283,6 +310,8 @@ void addReplyDouble(redisClient *c, double d) {
addReplyString(c,sbuf,slen);
}
/* Add a long long as integer reply or bulk len / multi bulk count.
* Basically this is used to output <prefix><long long><crlf>. */
void _addReplyLongLong(redisClient *c, long long ll, char prefix) {
char buf[128];
int len;
@@ -301,6 +330,7 @@ void addReplyMultiBulkLen(redisClient *c, long length) {
_addReplyLongLong(c,length,'*');
}
/* Create the length prefix of a bulk reply, example: $2234 */
void addReplyBulkLen(redisClient *c, robj *obj) {
size_t len;
@@ -322,23 +352,38 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
_addReplyLongLong(c,len,'$');
}
/* Add a Redis Object as a bulk reply */
void addReplyBulk(redisClient *c, robj *obj) {
addReplyBulkLen(c,obj);
addReply(c,obj);
addReply(c,shared.crlf);
}
/* In the CONFIG command we need to add vanilla C string as bulk replies */
/* Add a C buffer as bulk reply */
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) {
_addReplyLongLong(c,len,'$');
addReplyString(c,p,len);
addReply(c,shared.crlf);
}
/* Add a C nul term string as bulk reply */
void addReplyBulkCString(redisClient *c, char *s) {
if (s == NULL) {
addReply(c,shared.nullbulk);
} else {
robj *o = createStringObject(s,strlen(s));
addReplyBulk(c,o);
decrRefCount(o);
addReplyBulkCBuffer(c,s,strlen(s));
}
}
/* Add a long long as a bulk reply */
void addReplyBulkLongLong(redisClient *c, long long ll) {
char buf[64];
int len;
len = ll2string(buf,64,ll);
addReplyBulkCBuffer(c,buf,len);
}
static void acceptCommonHandler(int fd) {
redisClient *c;
if ((c = createClient(fd)) == NULL) {
@@ -372,7 +417,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
if (cfd == AE_ERR) {
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
@@ -387,7 +432,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
cfd = anetUnixAccept(server.neterr, fd);
if (cfd == AE_ERR) {
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
@@ -400,6 +445,7 @@ static void freeClientArgv(redisClient *c) {
for (j = 0; j < c->argc; j++)
decrRefCount(c->argv[j]);
c->argc = 0;
c->cmd = NULL;
}
void freeClient(redisClient *c) {
@@ -433,6 +479,13 @@ void freeClient(redisClient *c) {
ln = listSearchKey(server.clients,c);
redisAssert(ln != NULL);
listDelNode(server.clients,ln);
/* When client was just unblocked because of a blocking operation,
* remove it from the list with unblocked clients. */
if (c->flags & REDIS_UNBLOCKED) {
ln = listSearchKey(server.unblocked_clients,c);
redisAssert(ln != NULL);
listDelNode(server.unblocked_clients,ln);
}
/* Remove from the list of clients waiting for swapped keys, or ready
* to be restarted, but not yet woken up again. */
if (c->flags & REDIS_IO_WAIT) {
@@ -467,16 +520,21 @@ void freeClient(redisClient *c) {
/* Case 2: we lost the connection with the master. */
if (c->flags & REDIS_MASTER) {
server.master = NULL;
/* FIXME */
server.replstate = REDIS_REPL_CONNECT;
/* Since we lost the connection with the master, we should also
* close the connection with all our slaves if we have any, so
* when we'll resync with the master the other slaves will sync again
* with us as well. Note that also when the slave is not connected
* to the master it will keep refusing connections by other slaves. */
while (listLength(server.slaves)) {
ln = listFirst(server.slaves);
freeClient((redisClient*)ln->value);
* to the master it will keep refusing connections by other slaves.
*
* We do this only if server.masterhost != NULL. If it is NULL this
* means the user called SLAVEOF NO ONE and we are freeing our
* link with the master, so no need to close link with slaves. */
if (server.masterhost != NULL) {
while (listLength(server.slaves)) {
ln = listFirst(server.slaves);
freeClient((redisClient*)ln->value);
}
}
}
/* Release memory */
@@ -492,15 +550,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
/* Use writev() if we have enough buffers to send */
if (!server.glueoutputbuf &&
listLength(c->reply) > REDIS_WRITEV_THRESHOLD &&
!(c->flags & REDIS_MASTER))
{
sendReplyToClientWritev(el, fd, privdata, mask);
return;
}
while(c->bufpos > 0 || listLength(c->reply)) {
if (c->bufpos > 0) {
if (c->flags & REDIS_MASTER) {
@@ -562,7 +611,7 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
}
}
if (totwritten > 0) c->lastinteraction = time(NULL);
if (listLength(c->reply) == 0) {
if (c->bufpos == 0 && listLength(c->reply) == 0) {
c->sentlen = 0;
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
@@ -571,84 +620,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
}
}
void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask)
{
redisClient *c = privdata;
int nwritten = 0, totwritten = 0, objlen, willwrite;
robj *o;
struct iovec iov[REDIS_WRITEV_IOVEC_COUNT];
int offset, ion = 0;
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
listNode *node;
while (listLength(c->reply)) {
offset = c->sentlen;
ion = 0;
willwrite = 0;
/* fill-in the iov[] array */
for(node = listFirst(c->reply); node; node = listNextNode(node)) {
o = listNodeValue(node);
objlen = sdslen(o->ptr);
if (totwritten + objlen - offset > REDIS_MAX_WRITE_PER_EVENT)
break;
if(ion == REDIS_WRITEV_IOVEC_COUNT)
break; /* no more iovecs */
iov[ion].iov_base = ((char*)o->ptr) + offset;
iov[ion].iov_len = objlen - offset;
willwrite += objlen - offset;
offset = 0; /* just for the first item */
ion++;
}
if(willwrite == 0)
break;
/* write all collected blocks at once */
if((nwritten = writev(fd, iov, ion)) < 0) {
if (errno != EAGAIN) {
redisLog(REDIS_VERBOSE,
"Error writing to client: %s", strerror(errno));
freeClient(c);
return;
}
break;
}
totwritten += nwritten;
offset = c->sentlen;
/* remove written robjs from c->reply */
while (nwritten && listLength(c->reply)) {
o = listNodeValue(listFirst(c->reply));
objlen = sdslen(o->ptr);
if(nwritten >= objlen - offset) {
listDelNode(c->reply, listFirst(c->reply));
nwritten -= objlen - offset;
c->sentlen = 0;
} else {
/* partial write */
c->sentlen += nwritten;
break;
}
offset = 0;
}
}
if (totwritten > 0)
c->lastinteraction = time(NULL);
if (listLength(c->reply) == 0) {
c->sentlen = 0;
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
}
}
/* resetClient prepare the client to process the next command */
void resetClient(redisClient *c) {
freeClientArgv(c);
@@ -677,7 +648,7 @@ void closeTimedoutClients(void) {
redisLog(REDIS_VERBOSE,"Closing idle client");
freeClient(c);
} else if (c->flags & REDIS_BLOCKED) {
if (c->blockingto != 0 && c->blockingto < now) {
if (c->bpop.timeout != 0 && c->bpop.timeout < now) {
addReply(c,shared.nullmultibulk);
unblockClientWaitingData(c);
}
@@ -780,7 +751,7 @@ int processMultibulkBuffer(redisClient *c) {
bulklen = strtol(c->querybuf+pos+1,&eptr,10);
tolerr = (eptr[0] != '\r');
if (tolerr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
bulklen < 0 || bulklen > 1024*1024*1024)
bulklen < 0 || bulklen > 512*1024*1024)
{
addReplyError(c,"Protocol error: invalid bulk length");
setProtocolError(c,pos);
@@ -884,3 +855,46 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
}
processInputBuffer(c);
}
void getClientsMaxBuffers(unsigned long *longest_output_list,
unsigned long *biggest_input_buffer) {
redisClient *c;
listNode *ln;
listIter li;
unsigned long lol = 0, bib = 0;
listRewind(server.clients,&li);
while ((ln = listNext(&li)) != NULL) {
c = listNodeValue(ln);
if (listLength(c->reply) > lol) lol = listLength(c->reply);
if (sdslen(c->querybuf) > bib) bib = sdslen(c->querybuf);
}
*longest_output_list = lol;
*biggest_input_buffer = bib;
}
void rewriteClientCommandVector(redisClient *c, int argc, ...) {
va_list ap;
int j;
robj **argv; /* The new argument vector */
argv = zmalloc(sizeof(robj*)*argc);
va_start(ap,argc);
for (j = 0; j < argc; j++) {
robj *a;
a = va_arg(ap, robj*);
argv[j] = a;
incrRefCount(a);
}
/* We free the objects in the original vector at the end, so we are
* sure that if the same objects are reused in the new vector the
* refcount gets incremented before it gets decremented. */
for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]);
zfree(c->argv);
/* Replace argv and argc with our new versions. */
c->argv = argv;
c->argc = argc;
va_end(ap);
}
+44 -1
View File
@@ -93,10 +93,13 @@ robj *createHashObject(void) {
robj *createZsetObject(void) {
zset *zs = zmalloc(sizeof(*zs));
robj *o;
zs->dict = dictCreate(&zsetDictType,NULL);
zs->zsl = zslCreate();
return createObject(REDIS_ZSET,zs);
o = createObject(REDIS_ZSET,zs);
o->encoding = REDIS_ENCODING_SKIPLIST;
return o;
}
void freeStringObject(robj *o) {
@@ -425,6 +428,7 @@ char *strEncoding(int encoding) {
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
case REDIS_ENCODING_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset";
case REDIS_ENCODING_SKIPLIST: return "skiplist";
default: return "unknown";
}
}
@@ -439,3 +443,42 @@ unsigned long estimateObjectIdleTime(robj *o) {
REDIS_LRU_CLOCK_RESOLUTION;
}
}
/* This is an helper function for the DEBUG command. We need to lookup keys
* without any modification of LRU or other parameters. */
robj *objectCommandLookup(redisClient *c, robj *key) {
dictEntry *de;
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
return (robj*) dictGetEntryVal(de);
}
robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) {
robj *o = objectCommandLookup(c,key);
if (!o) addReply(c, reply);
return o;
}
/* Object command allows to inspect the internals of an Redis Object.
* Usage: OBJECT <verb> ... arguments ... */
void objectCommand(redisClient *c) {
robj *o;
if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyLongLong(c,o->refcount);
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyBulkCString(c,strEncoding(o->encoding));
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyLongLong(c,estimateObjectIdleTime(o));
} else {
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
}
}
+1 -1
View File
@@ -141,7 +141,7 @@ int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
/* Unsubscribe from all the channels. Return the number of channels the
* client was subscribed from. */
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
dictIterator *di = dictGetIterator(c->pubsub_channels);
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
dictEntry *de;
int count = 0;
+1 -1
View File
@@ -427,7 +427,7 @@ int rdbSave(char *filename) {
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetIterator(d);
di = dictGetSafeIterator(d);
if (!di) {
fclose(fp);
return REDIS_ERR;
+110 -144
View File
@@ -45,10 +45,6 @@
#include "adlist.h"
#include "zmalloc.h"
#define CLIENT_CONNECTING 0
#define CLIENT_SENDQUERY 1
#define CLIENT_READREPLY 2
#define REDIS_NOTUSED(V) ((void) V)
static struct config {
@@ -78,10 +74,10 @@ static struct config {
typedef struct _client {
redisContext *context;
int state;
sds obuf;
char *randptr[10]; /* needed for MSET against 10 keys */
size_t randlen;
unsigned int written; /* bytes of 'obuf' already written */
int replytype;
long long start; /* start time of a request */
long long latency; /* request latency */
} *client;
@@ -139,22 +135,17 @@ static void resetClient(client c) {
aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
c->written = 0;
c->state = CLIENT_SENDQUERY;
c->start = ustime();
c->latency = -1;
}
static void randomizeClientKey(client c) {
char *p;
char buf[32];
long r;
size_t i, r;
p = strstr(c->obuf, "_rand");
if (!p) return;
p += 5;
r = random() % config.randomkeys_keyspacelen;
sprintf(buf,"%ld",r);
memcpy(p,buf,strlen(buf));
for (i = 0; i < c->randlen; i++) {
r = random() % config.randomkeys_keyspacelen;
snprintf(buf,sizeof(buf),"%012zu",r);
memcpy(c->randptr[i],buf,12);
}
}
static void clientDone(client c) {
@@ -165,7 +156,6 @@ static void clientDone(client c) {
}
if (config.keepalive) {
resetClient(c);
if (config.randomkeys) randomizeClientKey(c);
} else {
config.liveclients--;
createMissingClients(c);
@@ -195,6 +185,11 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
exit(1);
}
if (reply != NULL) {
if (reply == (void*)REDIS_REPLY_ERROR) {
fprintf(stderr,"Unexpected error reply, exiting...\n");
exit(1);
}
if (config.donerequests < config.requests)
config.latency[config.donerequests++] = c->latency;
clientDone(c);
@@ -208,11 +203,13 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
if (c->state == CLIENT_CONNECTING) {
c->state = CLIENT_SENDQUERY;
/* When nothing was written yet, randomize keys and set start time. */
if (c->written == 0) {
if (config.randomkeys) randomizeClientKey(c);
c->start = ustime();
c->latency = -1;
}
if (sdslen(c->obuf) > c->written) {
void *ptr = c->obuf+c->written;
int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
@@ -226,12 +223,11 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
if (sdslen(c->obuf) == c->written) {
aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c);
c->state = CLIENT_READREPLY;
}
}
}
static client createClient(int replytype) {
static client createClient(char *cmd, int len) {
client c = zmalloc(sizeof(struct _client));
if (config.hostsocket == NULL) {
c->context = redisConnectNonBlock(config.hostip,config.hostport);
@@ -246,10 +242,22 @@ static client createClient(int replytype) {
fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
exit(1);
}
c->replytype = replytype;
c->state = CLIENT_CONNECTING;
c->obuf = sdsempty();
c->obuf = sdsnewlen(cmd,len);
c->randlen = 0;
c->written = 0;
/* Find substrings in the output buffer that need to be randomized. */
if (config.randomkeys) {
char *p = c->obuf, *newline;
while ((p = strstr(p,":rand:")) != NULL) {
newline = strstr(p,"\r\n");
assert(newline-(p+6) == 12); /* 12 chars for randomness */
assert(c->randlen < (signed)(sizeof(c->randptr)/sizeof(char*)));
c->randptr[c->randlen++] = p+6;
p = newline+2;
}
}
redisSetReplyObjectFunctions(c->context,NULL);
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
listAddNodeTail(config.clients,c);
@@ -258,11 +266,16 @@ static client createClient(int replytype) {
}
static void createMissingClients(client c) {
int n = 0;
while(config.liveclients < config.numclients) {
client new = createClient(c->replytype);
sdsfree(new->obuf);
new->obuf = sdsdup(c->obuf);
if (config.randomkeys) randomizeClientKey(c);
createClient(c->obuf,sdslen(c->obuf));
/* Listen backlog is quite limited on most systems */
if (++n > 64) {
usleep(50000);
n = 0;
}
}
}
@@ -298,14 +311,19 @@ static void showLatencyReport(void) {
}
}
static void prepareForBenchmark(char *title) {
config.title = title;
config.start = mstime();
config.donerequests = 0;
}
static void benchmark(char *title, char *cmd, int len) {
client c;
static void endBenchmark(void) {
config.title = title;
config.donerequests = 0;
c = createClient(cmd,len);
createMissingClients(c);
config.start = mstime();
aeMain(config.el);
config.totlatency = mstime()-config.start;
showLatencyReport();
freeAllClients();
}
@@ -392,6 +410,7 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
}
int main(int argc, char **argv) {
int i;
client c;
signal(SIGHUP, SIG_IGN);
@@ -426,136 +445,83 @@ int main(int argc, char **argv) {
if (config.idlemode) {
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
prepareForBenchmark("IDLE");
c = createClient(0); /* will never receive a reply */
c->obuf = sdsempty();
c = createClient("",0); /* will never receive a reply */
createMissingClients(c);
aeMain(config.el);
/* and will wait for every */
}
do {
prepareForBenchmark("PING");
c = createClient(REDIS_REPLY_STATUS);
c->obuf = sdscat(c->obuf,"PING\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
char *data, *cmd;
int len;
prepareForBenchmark("PING (multi bulk)");
c = createClient(REDIS_REPLY_STATUS);
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
data = zmalloc(config.datasize+1);
memset(data,'x',config.datasize);
data[config.datasize] = '\0';
prepareForBenchmark("MSET (10 keys, multi bulk)");
c = createClient(REDIS_REPLY_ARRAY);
c->obuf = sdscatprintf(c->obuf,"*%d\r\n$4\r\nMSET\r\n", 11);
{
int i;
char *data = zmalloc(config.datasize+2);
memset(data,'x',config.datasize);
for (i = 0; i < 10; i++) {
c->obuf = sdscatprintf(c->obuf,"$%d\r\n%s\r\n",config.datasize,data);
}
zfree(data);
benchmark("PING (inline)","PING\r\n",6);
len = redisFormatCommand(&cmd,"PING");
benchmark("PING",cmd,len);
free(cmd);
const char *argv[21];
argv[0] = "MSET";
for (i = 1; i < 21; i += 2) {
argv[i] = "foo:rand:000000000000";
argv[i+1] = data;
}
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommandArgv(&cmd,21,argv,NULL);
benchmark("MSET (10 keys)",cmd,len);
free(cmd);
prepareForBenchmark("SET");
c = createClient(REDIS_REPLY_STATUS);
c->obuf = sdscat(c->obuf,"*3\r\n$3\r\nSET\r\n$20\r\nfoo_rand000000000000\r\n");
{
char *data = zmalloc(config.datasize+2);
memset(data,'x',config.datasize);
data[config.datasize] = '\r';
data[config.datasize+1] = '\n';
c->obuf = sdscatprintf(c->obuf,"$%d\r\n",config.datasize);
c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
}
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data);
benchmark("SET",cmd,len);
free(cmd);
prepareForBenchmark("GET");
c = createClient(REDIS_REPLY_STRING);
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"GET foo:rand:000000000000");
benchmark("GET",cmd,len);
free(cmd);
prepareForBenchmark("INCR");
c = createClient(REDIS_REPLY_INTEGER);
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000");
benchmark("INCR",cmd,len);
free(cmd);
prepareForBenchmark("LPUSH");
c = createClient(REDIS_REPLY_INTEGER);
c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
benchmark("LPUSH",cmd,len);
free(cmd);
prepareForBenchmark("LPOP");
c = createClient(REDIS_REPLY_STRING);
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"LPOP mylist");
benchmark("LPOP",cmd,len);
free(cmd);
prepareForBenchmark("SADD");
c = createClient(REDIS_REPLY_STATUS);
c->obuf = sdscat(c->obuf,"SADD myset counter_rand000000000000\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"SADD myset counter:rand:000000000000");
benchmark("SADD",cmd,len);
free(cmd);
prepareForBenchmark("SPOP");
c = createClient(REDIS_REPLY_STRING);
c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"SPOP myset");
benchmark("SPOP",cmd,len);
free(cmd);
prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
c = createClient(REDIS_REPLY_STATUS);
c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
benchmark("LPUSH (again, in order to bench LRANGE)",cmd,len);
free(cmd);
prepareForBenchmark("LRANGE (first 100 elements)");
c = createClient(REDIS_REPLY_ARRAY);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"LRANGE mylist 0 99");
benchmark("LRANGE (first 100 elements)",cmd,len);
free(cmd);
prepareForBenchmark("LRANGE (first 300 elements)");
c = createClient(REDIS_REPLY_ARRAY);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"LRANGE mylist 0 299");
benchmark("LRANGE (first 300 elements)",cmd,len);
free(cmd);
prepareForBenchmark("LRANGE (first 450 elements)");
c = createClient(REDIS_REPLY_ARRAY);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"LRANGE mylist 0 449");
benchmark("LRANGE (first 450 elements)",cmd,len);
free(cmd);
prepareForBenchmark("LRANGE (first 600 elements)");
c = createClient(REDIS_REPLY_ARRAY);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
createMissingClients(c);
aeMain(config.el);
endBenchmark();
len = redisFormatCommand(&cmd,"LRANGE mylist 0 599");
benchmark("LRANGE (first 600 elements)",cmd,len);
free(cmd);
printf("\n");
} while(config.loop);
+255 -81
View File
@@ -55,21 +55,23 @@ static struct config {
int hostport;
char *hostsocket;
long repeat;
long interval;
int dbnum;
int interactive;
int shutdown;
int monitor_mode;
int pubsub_mode;
int raw_output; /* output mode per command */
int tty; /* flag for default output format */
int latency_mode;
int stdinarg; /* get last arg from stdin. (-x option) */
char mb_sep;
char *auth;
char *historyfile;
int raw_output; /* output mode per command */
sds mb_delim;
char prompt[32];
} config;
static void usage();
char *redisGitSHA1(void);
char *redisGitDirty(void);
/*------------------------------------------------------------------------------
* Utility functions
@@ -85,6 +87,15 @@ static long long mstime(void) {
return mst;
}
static void cliRefreshPrompt(void) {
if (config.dbnum == 0)
snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d> ",
config.hostip, config.hostport);
else
snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d[%d]> ",
config.hostip, config.hostport, config.dbnum);
}
/*------------------------------------------------------------------------------
* Help functions
*--------------------------------------------------------------------------- */
@@ -105,6 +116,20 @@ typedef struct {
static helpEntry *helpEntries;
static int helpEntriesLen;
static sds cliVersion() {
sds version;
version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
/* Add git commit and working tree status when available */
if (strtoll(redisGitSHA1(),NULL,16)) {
version = sdscatprintf(version, " (git:%s", redisGitSHA1());
if (strtoll(redisGitDirty(),NULL,10))
version = sdscatprintf(version, "-dirty");
version = sdscat(version, ")");
}
return version;
}
static void cliInitHelp() {
int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
int groupslen = sizeof(commandGroups)/sizeof(char*);
@@ -145,14 +170,16 @@ static void cliOutputCommandHelp(struct commandHelp *help, int group) {
/* Print generic help. */
static void cliOutputGenericHelp() {
sds version = cliVersion();
printf(
"redis-cli %s\r\n"
"Type: \"help @<group>\" to get a list of commands in <group>\r\n"
" \"help <command>\" for help on <command>\r\n"
" \"help <tab>\" to get a list of possible help topics\r\n"
" \"quit\" to exit\r\n",
REDIS_VERSION
version
);
sdsfree(version);
}
/* Output all command help, filtering by group or command name. */
@@ -248,11 +275,9 @@ static int cliAuth() {
/* Send SELECT dbnum to the server */
static int cliSelect() {
redisReply *reply;
char dbnum[16];
if (config.dbnum == 0) return REDIS_OK;
snprintf(dbnum,sizeof(dbnum),"%d",config.dbnum);
reply = redisCommand(context,"SELECT %s",dbnum);
reply = redisCommand(context,"SELECT %d",config.dbnum);
if (reply != NULL) {
freeReplyObject(reply);
return REDIS_OK;
@@ -293,36 +318,29 @@ static int cliConnect(int force) {
return REDIS_OK;
}
static void cliPrintContextErrorAndExit() {
static void cliPrintContextError() {
if (context == NULL) return;
fprintf(stderr,"Error: %s\n",context->errstr);
exit(1);
}
static sds cliFormatReply(redisReply *r, char *prefix) {
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
sds out = sdsempty();
switch (r->type) {
case REDIS_REPLY_ERROR:
if (config.tty) out = sdscat(out,"(error) ");
out = sdscatprintf(out,"%s\n", r->str);
out = sdscatprintf(out,"(error) %s\n", r->str);
break;
case REDIS_REPLY_STATUS:
out = sdscat(out,r->str);
out = sdscat(out,"\n");
break;
case REDIS_REPLY_INTEGER:
if (config.tty) out = sdscat(out,"(integer) ");
out = sdscatprintf(out,"%lld\n",r->integer);
out = sdscatprintf(out,"(integer) %lld\n",r->integer);
break;
case REDIS_REPLY_STRING:
if (config.raw_output || !config.tty) {
out = sdscatlen(out,r->str,r->len);
} else {
/* If you are producing output for the standard output we want
* a more interesting output with quoted characters and so forth */
out = sdscatrepr(out,r->str,r->len);
out = sdscat(out,"\n");
}
/* If you are producing output for the standard output we want
* a more interesting output with quoted characters and so forth */
out = sdscatrepr(out,r->str,r->len);
out = sdscat(out,"\n");
break;
case REDIS_REPLY_NIL:
out = sdscat(out,"(nil)\n");
@@ -358,7 +376,7 @@ static sds cliFormatReply(redisReply *r, char *prefix) {
out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
/* Format the multi bulk entry */
tmp = cliFormatReply(r->element[i],_prefix);
tmp = cliFormatReplyTTY(r->element[i],_prefix);
out = sdscatlen(out,tmp,sdslen(tmp));
sdsfree(tmp);
}
@@ -372,11 +390,46 @@ static sds cliFormatReply(redisReply *r, char *prefix) {
return out;
}
static int cliReadReply() {
static sds cliFormatReplyRaw(redisReply *r) {
sds out = sdsempty(), tmp;
size_t i;
switch (r->type) {
case REDIS_REPLY_NIL:
/* Nothing... */
break;
case REDIS_REPLY_ERROR:
out = sdscatlen(out,r->str,r->len);
out = sdscatlen(out,"\n",1);
break;
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
out = sdscatlen(out,r->str,r->len);
break;
case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"%lld",r->integer);
break;
case REDIS_REPLY_ARRAY:
for (i = 0; i < r->elements; i++) {
if (i > 0) out = sdscat(out,config.mb_delim);
tmp = cliFormatReplyRaw(r->element[i]);
out = sdscatlen(out,tmp,sdslen(tmp));
sdsfree(tmp);
}
break;
default:
fprintf(stderr,"Unknown reply type: %d\n", r->type);
exit(1);
}
return out;
}
static int cliReadReply(int output_raw_strings) {
void *_reply;
redisReply *reply;
sds out;
if (redisGetReply(context,(void**)&reply) != REDIS_OK) {
if (redisGetReply(context,&_reply) != REDIS_OK) {
if (config.shutdown)
return REDIS_OK;
if (config.interactive) {
@@ -386,28 +439,44 @@ static int cliReadReply() {
if (context->err == REDIS_ERR_EOF)
return REDIS_ERR;
}
cliPrintContextErrorAndExit();
cliPrintContextError();
exit(1);
return REDIS_ERR; /* avoid compiler warning */
}
out = cliFormatReply(reply,"");
freeReplyObject(reply);
reply = (redisReply*)_reply;
if (output_raw_strings) {
out = cliFormatReplyRaw(reply);
} else {
if (config.raw_output) {
out = cliFormatReplyRaw(reply);
out = sdscat(out,"\n");
} else {
out = cliFormatReplyTTY(reply,"");
}
}
fwrite(out,sdslen(out),1,stdout);
sdsfree(out);
freeReplyObject(reply);
return REDIS_OK;
}
static int cliSendCommand(int argc, char **argv, int repeat) {
char *command = argv[0];
size_t *argvlen;
int j;
int j, output_raw;
if (context == NULL) {
printf("Not connected, please use: connect <host> <port>\n");
return REDIS_OK;
if (context == NULL) return REDIS_ERR;
output_raw = 0;
if (!strcasecmp(command,"info") ||
(argc == 2 && !strcasecmp(command,"client") &&
!strcasecmp(argv[1],"list")))
{
output_raw = 1;
}
config.raw_output = !strcasecmp(command,"info");
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
cliOutputHelp(--argc, ++argv);
return REDIS_OK;
@@ -425,20 +494,33 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
while(repeat--) {
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
while (config.monitor_mode) {
if (cliReadReply() != REDIS_OK) exit(1);
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
fflush(stdout);
}
if (config.pubsub_mode) {
printf("Reading messages... (press Ctrl-C to quit)\n");
if (!config.raw_output)
printf("Reading messages... (press Ctrl-C to quit)\n");
while (1) {
if (cliReadReply() != REDIS_OK) exit(1);
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
}
}
if (cliReadReply() != REDIS_OK)
if (cliReadReply(output_raw) != REDIS_OK) {
free(argvlen);
return REDIS_ERR;
} else {
/* Store database number when SELECT was successfully executed. */
if (!strcasecmp(command,"select") && argc == 2) {
config.dbnum = atoi(argv[1]);
cliRefreshPrompt();
}
}
if (config.interval) usleep(config.interval);
fflush(stdout); /* Make it grep friendly */
}
free(argvlen);
return REDIS_OK;
}
@@ -458,6 +540,8 @@ static int parseOptions(int argc, char **argv) {
i++;
} else if (!strcmp(argv[i],"-h") && lastarg) {
usage();
} else if (!strcmp(argv[i],"--help")) {
usage();
} else if (!strcmp(argv[i],"-x")) {
config.stdinarg = 1;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
@@ -469,25 +553,28 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"-r") && !lastarg) {
config.repeat = strtoll(argv[i+1],NULL,10);
i++;
} else if (!strcmp(argv[i],"-i") && !lastarg) {
double seconds = atof(argv[i+1]);
config.interval = seconds*1000000;
i++;
} else if (!strcmp(argv[i],"-n") && !lastarg) {
config.dbnum = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-a") && !lastarg) {
config.auth = argv[i+1];
i++;
} else if (!strcmp(argv[i],"-i")) {
fprintf(stderr,
"Starting interactive mode using -i is deprecated. Interactive mode is started\n"
"by default when redis-cli is executed without a command to execute.\n"
);
} else if (!strcmp(argv[i],"-c")) {
fprintf(stderr,
"Reading last argument from standard input using -c is deprecated.\n"
"When standard input is connected to a pipe or regular file, it is\n"
"automatically used as last argument.\n"
);
} else if (!strcmp(argv[i],"-v")) {
printf("redis-cli shipped with Redis version %s (%s)\n", REDIS_VERSION, redisGitSHA1());
} else if (!strcmp(argv[i],"--raw")) {
config.raw_output = 1;
} else if (!strcmp(argv[i],"--latency")) {
config.latency_mode = 1;
} else if (!strcmp(argv[i],"-d") && !lastarg) {
sdsfree(config.mb_delim);
config.mb_delim = sdsnew(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
sds version = cliVersion();
printf("redis-cli %s\n", version);
sdsfree(version);
exit(0);
} else {
break;
@@ -514,12 +601,37 @@ static sds readArgFromStdin(void) {
}
static void usage() {
fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
fprintf(stderr, "example: redis-cli get my_passwd\n");
fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
sds version = cliVersion();
fprintf(stderr,
"redis-cli %s\n"
"\n"
"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
" -h <hostname> Server hostname (default: 127.0.0.1)\n"
" -p <port> Server port (default: 6379)\n"
" -s <socket> Server socket (overrides hostname and port)\n"
" -a <password> Password to use when connecting to the server\n"
" -r <repeat> Execute specified command N times\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
" It is possible to specify sub-second times like -i 0.1.\n"
" -n <db> Database number\n"
" -x Read last argument from STDIN\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n"
" --raw Use raw formatting for replies (default when STDOUT is not a tty)\n"
" --latency Enter a special mode continuously sampling latency.\n"
" --help Output this help and exit\n"
" --version Output version and exit\n"
"\n"
"Examples:\n"
" cat /etc/passwd | redis-cli -x set mypasswd\n"
" redis-cli get mypasswd\n"
" redis-cli -r 100 lpush mylist x\n"
" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
"\n"
"When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands.\n"
"\n",
version);
sdsfree(version);
exit(1);
}
@@ -536,18 +648,32 @@ static char **convertToSds(int count, char** args) {
#define LINE_BUFLEN 4096
static void repl() {
int argc, j;
sds historyfile = NULL;
int history = 0;
char *line;
int argc;
sds *argv;
config.interactive = 1;
linenoiseSetCompletionCallback(completionCallback);
while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) {
/* Only use history when stdin is a tty. */
if (isatty(fileno(stdin))) {
history = 1;
if (getenv("HOME") != NULL) {
historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
linenoiseHistoryLoad(historyfile);
}
}
cliRefreshPrompt();
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
if (line[0] != '\0') {
argv = sdssplitargs(line,&argc);
linenoiseHistoryAdd(line);
if (config.historyfile) linenoiseHistorySave(config.historyfile);
if (history) linenoiseHistoryAdd(line);
if (historyfile) linenoiseHistorySave(historyfile);
if (argv == NULL) {
printf("Invalid argument(s)\n");
continue;
@@ -561,16 +687,29 @@ static void repl() {
config.hostip = sdsnew(argv[1]);
config.hostport = atoi(argv[2]);
cliConnect(1);
} else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
linenoiseClearScreen();
} else {
long long start_time = mstime(), elapsed;
int repeat, skipargs = 0;
if (cliSendCommand(argc,argv,1) != REDIS_OK) {
repeat = atoi(argv[0]);
if (argc > 1 && repeat) {
skipargs = 1;
} else {
repeat = 1;
}
if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
!= REDIS_OK)
{
cliConnect(1);
/* If we still cannot send the command,
* print error and abort. */
if (cliSendCommand(argc,argv,1) != REDIS_OK)
cliPrintContextErrorAndExit();
/* If we still cannot send the command print error.
* We'll try to reconnect the next time. */
if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
!= REDIS_OK)
cliPrintContextError();
}
elapsed = mstime()-start_time;
if (elapsed >= 500) {
@@ -579,8 +718,7 @@ static void repl() {
}
}
/* Free the argument vector */
for (j = 0; j < argc; j++)
sdsfree(argv[j]);
while(argc--) sdsfree(argv[argc]);
zfree(argv);
}
/* linenoise() returns malloc-ed lines like readline() */
@@ -602,6 +740,38 @@ static int noninteractive(int argc, char **argv) {
return retval;
}
static void latencyMode(void) {
redisReply *reply;
long long start, latency, min, max, tot, count = 0;
double avg;
if (!context) exit(1);
while(1) {
start = mstime();
reply = redisCommand(context,"PING");
if (reply == NULL) {
fprintf(stderr,"\nI/O error\n");
exit(1);
}
latency = mstime()-start;
freeReplyObject(reply);
count++;
if (count == 1) {
min = max = tot = latency;
avg = (double) latency;
} else {
if (latency < min) min = latency;
if (latency > max) max = latency;
tot += latency;
avg = (double) tot/count;
}
printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)",
min, max, avg, count);
fflush(stdout);
usleep(10000);
}
}
int main(int argc, char **argv) {
int firstarg;
@@ -609,34 +779,38 @@ int main(int argc, char **argv) {
config.hostport = 6379;
config.hostsocket = NULL;
config.repeat = 1;
config.interval = 0;
config.dbnum = 0;
config.interactive = 0;
config.shutdown = 0;
config.monitor_mode = 0;
config.pubsub_mode = 0;
config.raw_output = 0;
config.latency_mode = 0;
config.stdinarg = 0;
config.auth = NULL;
config.historyfile = NULL;
config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
config.mb_sep = '\n';
config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL);
config.mb_delim = sdsnew("\n");
cliInitHelp();
if (getenv("HOME") != NULL) {
config.historyfile = malloc(256);
snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
linenoiseHistoryLoad(config.historyfile);
}
firstarg = parseOptions(argc,argv);
argc -= firstarg;
argv += firstarg;
/* Try to connect */
if (cliConnect(0) != REDIS_OK) exit(1);
/* Start in latency mode if appropriate */
if (config.latency_mode) {
cliConnect(0);
latencyMode();
}
/* Start interactive mode when no command is provided */
if (argc == 0) repl();
if (argc == 0) {
/* Note that in repl mode we don't abort on connection error.
* A new attempt will be performed for every command send. */
cliConnect(0);
repl();
}
/* Otherwise, we have some arguments to execute */
if (cliConnect(0) != REDIS_OK) exit(1);
return noninteractive(argc,convertToSds(argc,argv));
}
+157 -117
View File
@@ -28,6 +28,7 @@
*/
#include "redis.h"
#include "slowlog.h"
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
@@ -74,10 +75,14 @@ struct redisCommand readonlyCommandTable[] = {
{"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},
{"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0},
{"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"substr",substrCommand,4,0,NULL,1,1,1},
{"strlen",strlenCommand,2,0,NULL,1,1,1},
{"del",delCommand,-2,0,NULL,0,0,0},
{"exists",existsCommand,2,0,NULL,1,1,1},
{"setbit",setbitCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"getbit",getbitCommand,3,0,NULL,1,1,1},
{"setrange",setrangeCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"getrange",getrangeCommand,4,0,NULL,1,1,1},
{"substr",getrangeCommand,4,0,NULL,1,1,1},
{"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"mget",mgetCommand,-2,0,NULL,1,-1,1},
@@ -89,6 +94,7 @@ struct redisCommand readonlyCommandTable[] = {
{"rpop",rpopCommand,2,0,NULL,1,1,1},
{"lpop",lpopCommand,2,0,NULL,1,1,1},
{"brpop",brpopCommand,-3,0,NULL,1,1,1},
{"brpoplpush",brpoplpushCommand,4,REDIS_CMD_DENYOOM,NULL,1,2,1},
{"blpop",blpopCommand,-3,0,NULL,1,1,1},
{"llen",llenCommand,2,0,NULL,1,1,1},
{"lindex",lindexCommand,3,0,NULL,1,1,1},
@@ -96,7 +102,7 @@ struct redisCommand readonlyCommandTable[] = {
{"lrange",lrangeCommand,4,0,NULL,1,1,1},
{"ltrim",ltrimCommand,4,0,NULL,1,1,1},
{"lrem",lremCommand,4,0,NULL,1,1,1},
{"rpoplpush",rpoplpushcommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
{"rpoplpush",rpoplpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
{"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"srem",sremCommand,3,0,NULL,1,1,1},
{"smove",smoveCommand,4,0,NULL,1,2,1},
@@ -182,17 +188,21 @@ struct redisCommand readonlyCommandTable[] = {
{"punsubscribe",punsubscribeCommand,-1,0,NULL,0,0,0},
{"publish",publishCommand,3,REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0},
{"watch",watchCommand,-2,0,NULL,0,0,0},
{"unwatch",unwatchCommand,1,0,NULL,0,0,0}
{"unwatch",unwatchCommand,1,0,NULL,0,0,0},
{"object",objectCommand,-2,0,NULL,0,0,0},
{"slowlog",slowlogCommand,-2,0,NULL,0,0,0}
};
/*============================ Utility functions ============================ */
void redisLog(int level, const char *fmt, ...) {
const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
const char *c = ".-*#";
time_t now = time(NULL);
va_list ap;
FILE *fp;
char *c = ".-*#";
char buf[64];
time_t now;
char msg[REDIS_MAX_LOGMSG_LEN];
if (level < server.verbosity) return;
@@ -200,15 +210,16 @@ void redisLog(int level, const char *fmt, ...) {
if (!fp) return;
va_start(ap, fmt);
now = time(NULL);
strftime(buf,64,"%d %b %H:%M:%S",localtime(&now));
fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
vfprintf(fp, fmt, ap);
fprintf(fp,"\n");
fflush(fp);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
strftime(buf,sizeof(buf),"%d %b %H:%M:%S",localtime(&now));
fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg);
fflush(fp);
if (server.logfile) fclose(fp);
if (server.syslog_enabled) syslog(syslogLevelMap[level], "%s", msg);
}
/* Redis generally does not try to recover from out of memory conditions
@@ -508,7 +519,7 @@ void updateLRUClock(void) {
}
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
int j, loops = server.cronloops++;
int j, loops = server.cronloops;
REDIS_NOTUSED(eventLoop);
REDIS_NOTUSED(id);
REDIS_NOTUSED(clientData);
@@ -572,7 +583,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
}
/* Close connections of timedout clients */
if ((server.maxidletime && !(loops % 100)) || server.blpop_blocked_clients)
if ((server.maxidletime && !(loops % 100)) || server.bpop_blocked_clients)
closeTimedoutClients();
/* Check if a background saving or AOF rewrite in progress terminated */
@@ -637,6 +648,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* to detect transfer failures. */
if (!(loops % 10)) replicationCron();
server.cronloops++;
return 100;
}
@@ -645,15 +657,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* for ready file descriptors. */
void beforeSleep(struct aeEventLoop *eventLoop) {
REDIS_NOTUSED(eventLoop);
listNode *ln;
redisClient *c;
/* Awake clients that got all the swapped keys they requested */
if (server.vm_enabled && listLength(server.io_ready_clients)) {
listIter li;
listNode *ln;
listRewind(server.io_ready_clients,&li);
while((ln = listNext(&li))) {
redisClient *c = ln->value;
c = ln->value;
struct redisCommand *cmd;
/* Resume the client. */
@@ -664,13 +677,27 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
readQueryFromClient, c);
cmd = lookupCommand(c->argv[0]->ptr);
redisAssert(cmd != NULL);
call(c,cmd);
call(c);
resetClient(c);
/* There may be more data to process in the input buffer. */
if (c->querybuf && sdslen(c->querybuf) > 0)
processInputBuffer(c);
}
}
/* Try to process pending commands for clients that were just unblocked. */
while (listLength(server.unblocked_clients)) {
ln = listFirst(server.unblocked_clients);
redisAssert(ln != NULL);
c = ln->value;
listDelNode(server.unblocked_clients,ln);
c->flags &= ~REDIS_UNBLOCKED;
/* Process remaining data in the input buffer. */
if (c->querybuf && sdslen(c->querybuf) > 0)
processInputBuffer(c);
}
/* Write the AOF buffer on disk */
flushAppendOnlyFile();
}
@@ -743,7 +770,9 @@ void initServerConfig() {
server.saveparams = NULL;
server.loading = 0;
server.logfile = NULL; /* NULL = log on standard output */
server.glueoutputbuf = 1;
server.syslog_enabled = 0;
server.syslog_ident = zstrdup("redis");
server.syslog_facility = LOG_LOCAL0;
server.daemonize = 0;
server.appendonly = 0;
server.appendfsync = APPENDFSYNC_EVERYSEC;
@@ -758,7 +787,7 @@ void initServerConfig() {
server.rdbcompression = 1;
server.activerehashing = 1;
server.maxclients = 0;
server.blpop_blocked_clients = 0;
server.bpop_blocked_clients = 0;
server.maxmemory = 0;
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
server.maxmemory_samples = 3;
@@ -804,6 +833,10 @@ void initServerConfig() {
populateCommandTable();
server.delCommand = lookupCommandByCString("del");
server.multiCommand = lookupCommandByCString("multi");
/* Slow log */
server.slowlog_log_slower_than = REDIS_SLOWLOG_LOG_SLOWER_THAN;
server.slowlog_max_len = REDIS_SLOWLOG_MAX_LEN;
}
void initServer() {
@@ -811,19 +844,28 @@ void initServer() {
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
setupSigSegvAction();
setupSignalHandlers();
if (server.syslog_enabled) {
openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
server.syslog_facility);
}
server.mainthread = pthread_self();
server.clients = listCreate();
server.slaves = listCreate();
server.monitors = listCreate();
server.unblocked_clients = listCreate();
createSharedObjects();
server.el = aeCreateEventLoop();
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);
if (server.ipfd == ANET_ERR) {
redisLog(REDIS_WARNING, "Opening port: %s", server.neterr);
exit(1);
if (server.port != 0) {
server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);
if (server.ipfd == ANET_ERR) {
redisLog(REDIS_WARNING, "Opening port: %s", server.neterr);
exit(1);
}
}
if (server.unixsocket != NULL) {
unlink(server.unixsocket); /* don't care if this fails */
@@ -860,6 +902,7 @@ void initServer() {
server.stat_numcommands = 0;
server.stat_numconnections = 0;
server.stat_expiredkeys = 0;
server.stat_evictedkeys = 0;
server.stat_starttime = time(NULL);
server.stat_keyspace_misses = 0;
server.stat_keyspace_hits = 0;
@@ -880,6 +923,8 @@ void initServer() {
}
if (server.vm_enabled) vmInit();
slowlogInit();
srand(time(NULL)^getpid());
}
/* Populates the Redis Command Table starting from the hard coded list
@@ -913,16 +958,18 @@ struct redisCommand *lookupCommandByCString(char *s) {
}
/* Call() is the core of Redis execution of a command */
void call(redisClient *c, struct redisCommand *cmd) {
long long dirty;
void call(redisClient *c) {
long long dirty, start = ustime(), duration;
dirty = server.dirty;
cmd->proc(c);
c->cmd->proc(c);
dirty = server.dirty-dirty;
duration = ustime()-start;
slowlogPushEntryIfNeeded(c->argv,c->argc,duration);
if (server.appendonly && dirty)
feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc);
if ((dirty || cmd->flags & REDIS_CMD_FORCE_REPLICATION) &&
if (server.appendonly && dirty > 0)
feedAppendOnlyFile(c->cmd,c->db->id,c->argv,c->argc);
if ((dirty > 0 || c->cmd->flags & REDIS_CMD_FORCE_REPLICATION) &&
listLength(server.slaves))
replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc);
if (listLength(server.monitors))
@@ -939,8 +986,6 @@ void call(redisClient *c, struct redisCommand *cmd) {
* and other operations can be performed by the caller. Otherwise
* if 0 is returned the client was destroied (i.e. after QUIT). */
int processCommand(redisClient *c) {
struct redisCommand *cmd;
/* 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
@@ -952,21 +997,22 @@ int processCommand(redisClient *c) {
}
/* Now lookup the command and check ASAP about trivial error conditions
* such wrong arity, bad command name and so forth. */
cmd = lookupCommand(c->argv[0]->ptr);
if (!cmd) {
* such as wrong arity, bad command name and so forth. */
c->cmd = lookupCommand(c->argv[0]->ptr);
if (!c->cmd) {
addReplyErrorFormat(c,"unknown command '%s'",
(char*)c->argv[0]->ptr);
return REDIS_OK;
} else if ((cmd->arity > 0 && cmd->arity != c->argc) ||
(c->argc < -cmd->arity)) {
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
(c->argc < -c->cmd->arity)) {
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
cmd->name);
c->cmd->name);
return REDIS_OK;
}
/* Check if the user is authenticated */
if (server.requirepass && !c->authenticated && cmd->proc != authCommand) {
if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
{
addReplyError(c,"operation not permitted");
return REDIS_OK;
}
@@ -977,7 +1023,7 @@ int processCommand(redisClient *c) {
* keys in the dataset). If there are not the only thing we can do
* is returning an error. */
if (server.maxmemory) freeMemoryIfNeeded();
if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) &&
if (server.maxmemory && (c->cmd->flags & REDIS_CMD_DENYOOM) &&
zmalloc_used_memory() > server.maxmemory)
{
addReplyError(c,"command not allowed when used memory > 'maxmemory'");
@@ -987,8 +1033,10 @@ int processCommand(redisClient *c) {
/* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
if ((dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns) > 0)
&&
cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand &&
cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) {
c->cmd->proc != subscribeCommand &&
c->cmd->proc != unsubscribeCommand &&
c->cmd->proc != psubscribeCommand &&
c->cmd->proc != punsubscribeCommand) {
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");
return REDIS_OK;
}
@@ -997,7 +1045,7 @@ int processCommand(redisClient *c) {
* we are a slave with a broken link with master. */
if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED &&
server.repl_serve_stale_data == 0 &&
cmd->proc != infoCommand && cmd->proc != slaveofCommand)
c->cmd->proc != infoCommand && c->cmd->proc != slaveofCommand)
{
addReplyError(c,
"link with MASTER is down and slave-serve-stale-data is set to no");
@@ -1005,22 +1053,22 @@ int processCommand(redisClient *c) {
}
/* Loading DB? Return an error if the command is not INFO */
if (server.loading && cmd->proc != infoCommand) {
if (server.loading && c->cmd->proc != infoCommand) {
addReply(c, shared.loadingerr);
return REDIS_OK;
}
/* Exec the command */
if (c->flags & REDIS_MULTI &&
cmd->proc != execCommand && cmd->proc != discardCommand &&
cmd->proc != multiCommand && cmd->proc != watchCommand)
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
{
queueMultiCommand(c,cmd);
queueMultiCommand(c);
addReply(c,shared.queued);
} else {
if (server.vm_enabled && server.vm_max_threads > 0 &&
blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR;
call(c,cmd);
blockClientOnSwappedKeys(c)) return REDIS_ERR;
call(c);
}
return REDIS_OK;
}
@@ -1028,20 +1076,29 @@ int processCommand(redisClient *c) {
/*================================== Shutdown =============================== */
int prepareForShutdown() {
redisLog(REDIS_WARNING,"User requested shutdown, saving DB...");
redisLog(REDIS_WARNING,"User requested shutdown...");
/* Kill the saving child if there is a background saving in progress.
We want to avoid race conditions, for instance our saving child may
overwrite the synchronous saving did by SHUTDOWN. */
if (server.bgsavechildpid != -1) {
redisLog(REDIS_WARNING,"There is a live saving child. Killing it!");
redisLog(REDIS_WARNING,"There is a child saving an .rdb. Killing it!");
kill(server.bgsavechildpid,SIGKILL);
rdbRemoveTempFile(server.bgsavechildpid);
}
if (server.appendonly) {
/* Kill the AOF saving child as the AOF we already have may be longer
* but contains the full dataset anyway. */
if (server.bgrewritechildpid != -1) {
redisLog(REDIS_WARNING,
"There is a child rewriting the AOF. Killing it!");
kill(server.bgrewritechildpid,SIGKILL);
}
/* Append only file: fsync() the AOF and exit */
redisLog(REDIS_NOTICE,"Calling fsync() on the AOF file.");
aof_fsync(server.appendfd);
if (server.vm_enabled) unlink(server.vm_swap_file);
} else if (server.saveparamslen > 0) {
}
if (server.saveparamslen > 0) {
redisLog(REDIS_NOTICE,"Saving the final RDB snapshot before exiting.");
/* Snapshotting. Perform a SYNC SAVE and exit */
if (rdbSave(server.dbfilename) != REDIS_OK) {
/* Ooops.. error saving! The best we can do is to continue
@@ -1049,14 +1106,23 @@ int prepareForShutdown() {
* in the next cron() Redis will be notified that the background
* saving aborted, handling special stuff like slaves pending for
* synchronization... */
redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit");
redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit.");
return REDIS_ERR;
}
} else {
redisLog(REDIS_WARNING,"Not saving DB.");
}
if (server.daemonize) unlink(server.pidfile);
redisLog(REDIS_WARNING,"Server exit now, bye bye...");
if (server.vm_enabled) {
redisLog(REDIS_NOTICE,"Removing the swap file.");
unlink(server.vm_swap_file);
}
if (server.daemonize) {
redisLog(REDIS_NOTICE,"Removing the pid file.");
unlink(server.pidfile);
}
/* Close the listening sockets. Apparently this allows faster restarts. */
if (server.ipfd != -1) close(server.ipfd);
if (server.sofd != -1) close(server.sofd);
redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");
return REDIS_OK;
}
@@ -1110,9 +1176,11 @@ sds genRedisInfoString(void) {
int j;
char hmem[64];
struct rusage self_ru, c_ru;
unsigned long lol, bib;
getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru);
getClientsMaxBuffers(&lol,&bib);
bytesToHuman(hmem,zmalloc_used_memory());
info = sdscatprintf(sdsempty(),
@@ -1127,10 +1195,12 @@ sds genRedisInfoString(void) {
"lru_clock:%ld\r\n"
"used_cpu_sys:%.2f\r\n"
"used_cpu_user:%.2f\r\n"
"used_cpu_sys_childrens:%.2f\r\n"
"used_cpu_user_childrens:%.2f\r\n"
"used_cpu_sys_children:%.2f\r\n"
"used_cpu_user_children:%.2f\r\n"
"connected_clients:%d\r\n"
"connected_slaves:%d\r\n"
"client_longest_output_list:%lu\r\n"
"client_biggest_input_buf:%lu\r\n"
"blocked_clients:%d\r\n"
"used_memory:%zu\r\n"
"used_memory_human:%s\r\n"
@@ -1146,6 +1216,7 @@ sds genRedisInfoString(void) {
"total_connections_received:%lld\r\n"
"total_commands_processed:%lld\r\n"
"expired_keys:%lld\r\n"
"evicted_keys:%lld\r\n"
"keyspace_hits:%lld\r\n"
"keyspace_misses:%lld\r\n"
"hash_max_zipmap_entries:%zu\r\n"
@@ -1169,7 +1240,8 @@ sds genRedisInfoString(void) {
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves),
server.blpop_blocked_clients,
lol, bib,
server.bpop_blocked_clients,
zmalloc_used_memory(),
hmem,
zmalloc_get_rss(),
@@ -1188,6 +1260,7 @@ sds genRedisInfoString(void) {
server.stat_numconnections,
server.stat_numcommands,
server.stat_expiredkeys,
server.stat_evictedkeys,
server.stat_keyspace_hits,
server.stat_keyspace_misses,
server.hash_max_zipmap_entries,
@@ -1280,6 +1353,7 @@ sds genRedisInfoString(void) {
eta
);
}
for (j = 0; j < server.dbnum; j++) {
long long keys, vkeys;
@@ -1404,45 +1478,15 @@ void freeMemoryIfNeeded(void) {
/* Finally remove the selected key. */
if (bestkey) {
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
propagateExpire(db,keyobj);
dbDelete(db,keyobj);
server.stat_expiredkeys++;
server.stat_evictedkeys++;
decrRefCount(keyobj);
freed++;
}
}
if (!freed) return; /* nothing to free... */
}
while(0) {
int j, k, freed = 0;
for (j = 0; j < server.dbnum; j++) {
int minttl = -1;
sds minkey = NULL;
robj *keyobj = NULL;
struct dictEntry *de;
if (dictSize(server.db[j].expires)) {
freed = 1;
/* From a sample of three keys drop the one nearest to
* the natural expire */
for (k = 0; k < 3; k++) {
time_t t;
de = dictGetRandomKey(server.db[j].expires);
t = (time_t) dictGetEntryVal(de);
if (minttl == -1 || t < minttl) {
minkey = dictGetEntryKey(de);
minttl = t;
}
}
keyobj = createStringObject(minkey,sdslen(minkey));
dbDelete(server.db+j,keyobj);
server.stat_expiredkeys++;
decrRefCount(keyobj);
}
}
if (!freed) return; /* nothing to free... */
}
}
/* =================================== Main! ================================ */
@@ -1473,7 +1517,7 @@ void createPidFile(void) {
/* Try to write the pid file in a best-effort way. */
FILE *fp = fopen(server.pidfile,"w");
if (fp) {
fprintf(fp,"%d\n",getpid());
fprintf(fp,"%d\n",(int)getpid());
fclose(fp);
}
}
@@ -1547,10 +1591,8 @@ int main(int argc, char **argv) {
return 0;
}
/* ============================= Backtrace support ========================= */
#ifdef HAVE_BACKTRACE
void *getMcontextEip(ucontext_t *uc) {
static void *getMcontextEip(ucontext_t *uc) {
#if defined(__FreeBSD__)
return (void*) uc->uc_mcontext.mc_eip;
#elif defined(__dietlibc__)
@@ -1578,7 +1620,7 @@ void *getMcontextEip(ucontext_t *uc) {
#endif
}
void segvHandler(int sig, siginfo_t *info, void *secret) {
static void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
void *trace[100];
char **messages = NULL;
int i, trace_size = 0;
@@ -1617,37 +1659,35 @@ void segvHandler(int sig, siginfo_t *info, void *secret) {
sigaction (sig, &act, NULL);
kill(getpid(),sig);
}
#endif /* HAVE_BACKTRACE */
void sigtermHandler(int sig) {
static void sigtermHandler(int sig) {
REDIS_NOTUSED(sig);
redisLog(REDIS_WARNING,"SIGTERM received, scheduling shutting down...");
redisLog(REDIS_WARNING,"Received SIGTERM, scheduling shutdown...");
server.shutdown_asap = 1;
}
void setupSigSegvAction(void) {
void setupSignalHandlers(void) {
struct sigaction act;
sigemptyset (&act.sa_mask);
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction
* is used. Otherwise, sa_handler is used */
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_SIGINFO;
act.sa_sigaction = segvHandler;
sigaction (SIGSEGV, &act, NULL);
sigaction (SIGBUS, &act, NULL);
sigaction (SIGFPE, &act, NULL);
sigaction (SIGILL, &act, NULL);
sigaction (SIGBUS, &act, NULL);
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
* Otherwise, sa_handler is used. */
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
act.sa_handler = sigtermHandler;
sigaction (SIGTERM, &act, NULL);
sigaction(SIGTERM, &act, NULL);
#ifdef HAVE_BACKTRACE
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_SIGINFO;
act.sa_sigaction = sigsegvHandler;
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGILL, &act, NULL);
#endif
return;
}
#else /* HAVE_BACKTRACE */
void setupSigSegvAction(void) {
}
#endif /* HAVE_BACKTRACE */
/* The End */
+58 -28
View File
@@ -17,6 +17,7 @@
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <syslog.h>
#include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */
@@ -47,11 +48,9 @@
#define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */
#define REDIS_SHARED_INTEGERS 10000
#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
/* If more then REDIS_WRITEV_THRESHOLD write packets are pending use writev */
#define REDIS_WRITEV_THRESHOLD 3
/* Max number of iovecs used for each writev call */
#define REDIS_WRITEV_IOVEC_COUNT 256
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
#define REDIS_SLOWLOG_LOG_SLOWER_THAN 10000
#define REDIS_SLOWLOG_MAX_LEN 64
/* Hash table parameters */
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
@@ -84,6 +83,7 @@
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
/* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME 253
@@ -144,6 +144,8 @@
#define REDIS_IO_WAIT 32 /* The client is waiting for Virtual Memory I/O */
#define REDIS_DIRTY_CAS 64 /* Watched keys modified. EXEC will fail. */
#define REDIS_CLOSE_AFTER_REPLY 128 /* Close after writing entire reply. */
#define REDIS_UNBLOCKED 256 /* This client was unblocked and is stored in
server.unblocked_clients */
/* Client request types */
#define REDIS_REQ_INLINE 1
@@ -192,11 +194,11 @@
#define APPENDFSYNC_EVERYSEC 2
/* Zip structure related defaults */
#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 64
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32
#define REDIS_SET_MAX_INTSET_ENTRIES 4096
#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 512
#define REDIS_HASH_MAX_ZIPMAP_VALUE 64
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
#define REDIS_LIST_MAX_ZIPLIST_VALUE 64
#define REDIS_SET_MAX_INTSET_ENTRIES 512
/* Sets operations codes */
#define REDIS_OP_UNION 0
@@ -293,6 +295,16 @@ typedef struct multiState {
int count; /* Total number of MULTI commands */
} multiState;
typedef struct blockingState {
robj **keys; /* The key we are waiting to terminate a blocking
* operation such as BLPOP. Otherwise NULL. */
int count; /* Number of blocking keys */
time_t timeout; /* Blocking operation timeout. If UNIX current time
* is >= timeout then the operation timed out. */
robj *target; /* The key that should receive the element,
* for BRPOPLPUSH. */
} blockingState;
/* With multiplexing we need to take per-clinet state.
* Clients are taken in a liked list. */
typedef struct redisClient {
@@ -302,6 +314,7 @@ typedef struct redisClient {
sds querybuf;
int argc;
robj **argv;
struct redisCommand *cmd;
int reqtype;
int multibulklen; /* number of multi bulk arguments left to read */
long bulklen; /* length of bulk argument in multi bulk request */
@@ -316,11 +329,7 @@ typedef struct redisClient {
long repldboff; /* replication DB file offset */
off_t repldbsize; /* replication DB file size */
multiState mstate; /* MULTI/EXEC state */
robj **blocking_keys; /* The key we are waiting to terminate a blocking
* operation such as BLPOP. Otherwise NULL. */
int blocking_keys_num; /* Number of blocking keys */
time_t blockingto; /* Blocking operation timeout. If UNIX current time
* is >= blockingto then the operation timed out. */
blockingState bpop; /* blocking state */
list *io_keys; /* Keys this client is waiting to be loaded from the
* swap file in order to continue. */
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
@@ -379,11 +388,15 @@ struct redisServer {
long long stat_numcommands; /* number of processed commands */
long long stat_numconnections; /* number of connections received */
long long stat_expiredkeys; /* number of expired keys */
long long stat_evictedkeys; /* number of evicted keys (maxmemory) */
long long stat_keyspace_hits; /* number of successful lookups of keys */
long long stat_keyspace_misses; /* number of failed lookups of keys */
list *slowlog;
long long slowlog_entry_id;
long long slowlog_log_slower_than;
unsigned long slowlog_max_len;
/* Configuration */
int verbosity;
int glueoutputbuf;
int maxidletime;
int dbnum;
int daemonize;
@@ -402,6 +415,9 @@ struct redisServer {
struct saveparam *saveparams;
int saveparamslen;
char *logfile;
int syslog_enabled;
char *syslog_ident;
int syslog_facility;
char *dbfilename;
char *appendfilename;
char *requirepass;
@@ -427,8 +443,9 @@ struct redisServer {
int maxmemory_policy;
int maxmemory_samples;
/* Blocked clients */
unsigned int blpop_blocked_clients;
unsigned int bpop_blocked_clients;
unsigned int vm_blocked_clients;
list *unblocked_clients;
/* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */
int sort_desc;
@@ -628,7 +645,6 @@ void closeTimedoutClients(void);
void freeClient(redisClient *c);
void resetClient(redisClient *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
void *addDeferredMultiBulkLength(redisClient *c);
void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
@@ -639,6 +655,8 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
void addReplyBulk(redisClient *c, robj *obj);
void addReplyBulkCString(redisClient *c, char *s);
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len);
void addReplyBulkLongLong(redisClient *c, long long ll);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
void addReplySds(redisClient *c, sds s);
@@ -648,6 +666,9 @@ void addReplyDouble(redisClient *c, double d);
void addReplyLongLong(redisClient *c, long long ll);
void addReplyMultiBulkLen(redisClient *c, long length);
void *dupClientReplyValue(void *o);
void getClientsMaxBuffers(unsigned long *longest_output_list,
unsigned long *biggest_input_buffer);
void rewriteClientCommandVector(redisClient *c, int argc, ...);
#ifdef __GNUC__
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
@@ -680,7 +701,7 @@ void popGenericCommand(redisClient *c, int where);
void unwatchAllKeys(redisClient *c);
void initClientMultiState(redisClient *c);
void freeClientMultiState(redisClient *c);
void queueMultiCommand(redisClient *c, struct redisCommand *cmd);
void queueMultiCommand(redisClient *c);
void touchWatchedKey(redisDb *db, robj *key);
void touchWatchedKeysOnFlush(int dbid);
@@ -765,10 +786,10 @@ zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
/* Core functions */
void freeMemoryIfNeeded(void);
int processCommand(redisClient *c);
void setupSigSegvAction(void);
void setupSignalHandlers(void);
struct redisCommand *lookupCommand(sds name);
struct redisCommand *lookupCommandByCString(char *s);
void call(redisClient *c, struct redisCommand *cmd);
void call(redisClient *c);
int prepareForShutdown();
void redisLog(int level, const char *fmt, ...);
void usage();
@@ -799,7 +820,7 @@ void vmReopenSwapFile(void);
int vmFreePage(off_t page);
void zunionInterBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv);
int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd);
int blockClientOnSwappedKeys(redisClient *c);
int dontWaitForSwappedKey(redisClient *c, robj *key);
void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key);
vmpointer *vmSwapObjectBlocking(robj *val);
@@ -811,8 +832,9 @@ int setTypeRemove(robj *subject, robj *value);
int setTypeIsMember(robj *subject, robj *value);
setTypeIterator *setTypeInitIterator(robj *subject);
void setTypeReleaseIterator(setTypeIterator *si);
robj *setTypeNext(setTypeIterator *si);
robj *setTypeRandomElement(robj *subject);
int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele);
robj *setTypeNextObject(setTypeIterator *si);
int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele);
unsigned long setTypeSize(robj *subject);
void setTypeConvert(robj *subject, int enc);
@@ -820,7 +842,8 @@ void setTypeConvert(robj *subject, int enc);
void convertToRealHash(robj *o);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);
robj *hashTypeGet(robj *o, robj *key);
int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v, unsigned int *vlen);
robj *hashTypeGetObject(robj *o, robj *key);
int hashTypeExists(robj *o, robj *key);
int hashTypeSet(robj *o, robj *key, robj *value);
int hashTypeDelete(robj *o, robj *key);
@@ -828,7 +851,8 @@ unsigned long hashTypeLength(robj *o);
hashTypeIterator *hashTypeInitIterator(robj *subject);
void hashTypeReleaseIterator(hashTypeIterator *hi);
int hashTypeNext(hashTypeIterator *hi);
robj *hashTypeCurrent(hashTypeIterator *hi, int what);
int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen);
robj *hashTypeCurrentObject(hashTypeIterator *hi, int what);
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key);
/* Pub / Sub */
@@ -846,6 +870,7 @@ int ll2string(char *s, size_t len, long long value);
int isStringRepresentableAsLong(sds s, long *longval);
int isStringRepresentableAsLongLong(sds s, long long *longval);
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
long long ustime(void);
/* Configuration */
void loadServerConfig(char *filename);
@@ -885,6 +910,10 @@ void setexCommand(redisClient *c);
void getCommand(redisClient *c);
void delCommand(redisClient *c);
void existsCommand(redisClient *c);
void setbitCommand(redisClient *c);
void getbitCommand(redisClient *c);
void setrangeCommand(redisClient *c);
void getrangeCommand(redisClient *c);
void incrCommand(redisClient *c);
void decrCommand(redisClient *c);
void incrbyCommand(redisClient *c);
@@ -932,7 +961,7 @@ void flushdbCommand(redisClient *c);
void flushallCommand(redisClient *c);
void sortCommand(redisClient *c);
void lremCommand(redisClient *c);
void rpoplpushcommand(redisClient *c);
void rpoplpushCommand(redisClient *c);
void infoCommand(redisClient *c);
void mgetCommand(redisClient *c);
void monitorCommand(redisClient *c);
@@ -961,8 +990,8 @@ void execCommand(redisClient *c);
void discardCommand(redisClient *c);
void blpopCommand(redisClient *c);
void brpopCommand(redisClient *c);
void brpoplpushCommand(redisClient *c);
void appendCommand(redisClient *c);
void substrCommand(redisClient *c);
void strlenCommand(redisClient *c);
void zrankCommand(redisClient *c);
void zrevrankCommand(redisClient *c);
@@ -989,6 +1018,7 @@ void punsubscribeCommand(redisClient *c);
void publishCommand(redisClient *c);
void watchCommand(redisClient *c);
void unwatchCommand(redisClient *c);
void objectCommand(redisClient *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
+51 -4
View File
@@ -88,7 +88,7 @@ void replicationFeedMonitors(list *monitors, int dictid, robj **argv, int argc)
struct timeval tv;
gettimeofday(&tv,NULL);
cmdrepr = sdscatprintf(cmdrepr,"%ld.%ld ",(long)tv.tv_sec,(long)tv.tv_usec);
cmdrepr = sdscatprintf(cmdrepr,"%ld.%06ld ",(long)tv.tv_sec,(long)tv.tv_usec);
if (dictid != 0) cmdrepr = sdscatprintf(cmdrepr,"(db %d) ", dictid);
for (j = 0; j < argc; j++) {
@@ -328,6 +328,12 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
buf+1);
replicationAbortSyncTransfer();
return;
} else if (buf[0] == '\0') {
/* At this stage just a newline works as a PING in order to take
* the connection live. So we refresh our last interaction
* timestamp. */
server.repl_transfer_lastio = time(NULL);
return;
} else if (buf[0] != '$') {
redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?");
replicationAbortSyncTransfer();
@@ -384,6 +390,8 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
server.master->authenticated = 1;
server.replstate = REDIS_REPL_CONNECTED;
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
/* Rewrite the AOF file now that the dataset changed. */
if (server.appendonly) rewriteAppendOnlyFileBackground();
}
}
@@ -488,23 +496,62 @@ void slaveofCommand(redisClient *c) {
/* --------------------------- REPLICATION CRON ---------------------------- */
#define REDIS_REPL_TRANSFER_TIMEOUT 60
#define REDIS_REPL_TIMEOUT 60
#define REDIS_REPL_PING_SLAVE_PERIOD 10
void replicationCron(void) {
/* Bulk transfer I/O timeout? */
if (server.masterhost && server.replstate == REDIS_REPL_TRANSFER &&
(time(NULL)-server.repl_transfer_lastio) > REDIS_REPL_TRANSFER_TIMEOUT)
(time(NULL)-server.repl_transfer_lastio) > REDIS_REPL_TIMEOUT)
{
redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER...");
replicationAbortSyncTransfer();
}
/* Timed out master when we are an already connected slave? */
if (server.masterhost && server.replstate == REDIS_REPL_CONNECTED &&
(time(NULL)-server.master->lastinteraction) > REDIS_REPL_TIMEOUT)
{
redisLog(REDIS_WARNING,"MASTER time out: no data nor PING received...");
freeClient(server.master);
}
/* Check if we should connect to a MASTER */
if (server.replstate == REDIS_REPL_CONNECT) {
redisLog(REDIS_NOTICE,"Connecting to MASTER...");
if (syncWithMaster() == REDIS_OK) {
redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started: SYNC sent");
if (server.appendonly) rewriteAppendOnlyFileBackground();
}
}
/* If we have attached slaves, PING them from time to time.
* So slaves can implement an explicit timeout to masters, and will
* be able to detect a link disconnection even if the TCP connection
* will not actually go down. */
if (!(server.cronloops % (REDIS_REPL_PING_SLAVE_PERIOD*10))) {
listIter li;
listNode *ln;
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
redisClient *slave = ln->value;
/* Don't ping slaves that are in the middle of a bulk transfer
* with the master for first synchronization. */
if (slave->replstate == REDIS_REPL_SEND_BULK) continue;
if (slave->replstate == REDIS_REPL_ONLINE) {
/* If the slave is online send a normal ping */
addReplySds(slave,sdsnew("PING\r\n"));
} else {
/* Otherwise we are in the pre-synchronization stage.
* Just a newline will do the work of refreshing the
* connection last interaction time, and at the same time
* we'll be sure that being a single char there are no
* short-write problems. */
if (write(slave->fd, "\n", 1) == -1) {
/* Don't worry, it's just a ping. */
}
}
}
}
}
+67 -1
View File
@@ -26,6 +26,12 @@
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* History:
*
* - 22 March 2011: History section created on top of sds.c
* - 22 March 2011: Fixed a problem with "\xab" escapes convertion in
* function sdssplitargs().
*/
#define SDS_ABORT_ON_OOM
@@ -116,6 +122,25 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
return newsh->buf;
}
/* Grow the sds to have the specified length. Bytes that were not part of
* the original length of the sds will be set to zero. */
sds sdsgrowzero(sds s, size_t len) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
size_t totlen, curlen = sh->len;
if (len <= curlen) return s;
s = sdsMakeRoomFor(s,len-curlen);
if (s == NULL) return NULL;
/* Make sure added region doesn't contain garbage */
sh = (void*)(s-(sizeof(struct sdshdr)));
memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
totlen = sh->len+sh->free;
sh->len = len;
sh->free = totlen-sh->len;
return s;
}
sds sdscatlen(sds s, void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
@@ -393,6 +418,37 @@ sds sdscatrepr(sds s, char *p, size_t len) {
return sdscatlen(s,"\"",1);
}
/* Helper function for sdssplitargs() that returns non zero if 'c'
* is a valid hex digit. */
int is_hex_digit(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
}
/* Helper function for sdssplitargs() that converts an hex digit into an
* integer from 0 to 15 */
int hex_digit_to_int(char c) {
switch(c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default: return 0;
}
}
/* Split a line into arguments, where every argument can be in the
* following programming-language REPL-alike form:
*
@@ -422,7 +478,17 @@ sds *sdssplitargs(char *line, int *argc) {
if (current == NULL) current = sdsempty();
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1)) {
if (*p == '\\' && *(p+1) == 'x' &&
is_hex_digit(*(p+2)) &&
is_hex_digit(*(p+3)))
{
unsigned char byte;
byte = (hex_digit_to_int(*(p+2))*16)+
hex_digit_to_int(*(p+3));
current = sdscatlen(current,(char*)&byte,1);
p += 3;
} else if (*p == '\\' && *(p+1)) {
char c;
p++;
+1
View File
@@ -49,6 +49,7 @@ size_t sdslen(const sds s);
sds sdsdup(const sds s);
void sdsfree(sds s);
size_t sdsavail(sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, void *t, size_t len);
sds sdscat(sds s, char *t);
sds sdscpylen(sds s, char *t, size_t len);
+115
View File
@@ -0,0 +1,115 @@
#include "redis.h"
#include "slowlog.h"
/* Slowlog implements a system that is able to remember the latest N
* queries that took more than M microseconds to execute.
*
* The execution time to reach to be logged in the slow log is set
* using the 'slowlog-log-slower-than' config directive, that is also
* readable and writable using the CONFIG SET/GET command.
*
* The slow queries log is actually not "logged" in the Redis log file
* but is accessible thanks to the SLOWLOG command. */
/* Create a new slowlog entry.
* Incrementing the ref count of all the objects retained is up to
* this function. */
slowlogEntry *slowlogCreateEntry(robj **argv, int argc, long long duration) {
slowlogEntry *se = zmalloc(sizeof(*se));
int j;
se->argc = argc;
se->argv = zmalloc(sizeof(robj*)*argc);
for (j = 0; j < argc; j++) {
se->argv[j] = argv[j];
incrRefCount(argv[j]);
}
se->time = time(NULL);
se->duration = duration;
se->id = server.slowlog_entry_id++;
return se;
}
/* Free a slow log entry. The argument is void so that the prototype of this
* function matches the one of the 'free' method of adlist.c.
*
* This function will take care to release all the retained object. */
void slowlogFreeEntry(void *septr) {
slowlogEntry *se = septr;
int j;
for (j = 0; j < se->argc; j++)
decrRefCount(se->argv[j]);
zfree(se->argv);
zfree(se);
}
/* Initialize the slow log. This function should be called a single time
* at server startup. */
void slowlogInit(void) {
server.slowlog = listCreate();
server.slowlog_entry_id = 0;
listSetFreeMethod(server.slowlog,slowlogFreeEntry);
}
/* Push a new entry into the slow log.
* This function will make sure to trim the slow log accordingly to the
* configured max length. */
void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration) {
if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
if (duration >= server.slowlog_log_slower_than)
listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration));
/* Remove old entries if needed. */
while (listLength(server.slowlog) > server.slowlog_max_len)
listDelNode(server.slowlog,listLast(server.slowlog));
}
/* Remove all the entries from the current slow log. */
void slowlogReset(void) {
while (listLength(server.slowlog) > 0)
listDelNode(server.slowlog,listLast(server.slowlog));
}
/* The SLOWLOG command. Implements all the subcommands needed to handle the
* Redis slow log. */
void slowlogCommand(redisClient *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) {
slowlogReset();
addReply(c,shared.ok);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) {
addReplyLongLong(c,listLength(server.slowlog));
} else if ((c->argc == 2 || c->argc == 3) &&
!strcasecmp(c->argv[1]->ptr,"get"))
{
long count = 10, sent = 0;
listIter li;
void *totentries;
listNode *ln;
slowlogEntry *se;
if (c->argc == 3 &&
getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != REDIS_OK)
return;
listRewind(server.slowlog,&li);
totentries = addDeferredMultiBulkLength(c);
while(count-- && (ln = listNext(&li))) {
int j;
se = ln->value;
addReplyMultiBulkLen(c,4);
addReplyLongLong(c,se->id);
addReplyLongLong(c,se->time);
addReplyLongLong(c,se->duration);
addReplyMultiBulkLen(c,se->argc);
for (j = 0; j < se->argc; j++)
addReplyBulk(c,se->argv[j]);
sent++;
}
setDeferredMultiBulkLength(c,totentries,sent);
} else {
addReplyError(c,
"Unknown SLOWLOG subcommand or wrong # of args. Try GET, RESET, LEN.");
}
}
+15
View File
@@ -0,0 +1,15 @@
/* This structure defines an entry inside the slow log list */
typedef struct slowlogEntry {
robj **argv;
int argc;
long long id; /* Unique entry identifier. */
long long duration; /* Time spent by the query, in nanoseconds. */
time_t time; /* Unix time at which the query was executed. */
} slowlogEntry;
/* Exported API */
void slowlogInit(void);
void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration);
/* Exported commands */
void slowlogCommand(redisClient *c);
+3 -2
View File
@@ -76,7 +76,7 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
/* Retrieve value from hash by the field name. This operation
* already increases the refcount of the returned object. */
initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(struct sdshdr)));
o = hashTypeGet(o, &fieldobj);
o = hashTypeGetObject(o, &fieldobj);
} else {
if (o->type != REDIS_STRING) return NULL;
@@ -222,7 +222,7 @@ void sortCommand(redisClient *c) {
} else if (sortval->type == REDIS_SET) {
setTypeIterator *si = setTypeInitIterator(sortval);
robj *ele;
while((ele = setTypeNext(si)) != NULL) {
while((ele = setTypeNextObject(si)) != NULL) {
vector[j].obj = ele;
vector[j].u.score = 0;
vector[j].u.cmpobj = NULL;
@@ -363,6 +363,7 @@ void sortCommand(redisClient *c) {
}
}
}
lookupKeyWrite(c->db,storekey); /* Force expire of old key if needed. */
dbReplace(c->db,storekey,sobj);
/* Note: we add 1 because the DB is dirty anyway since even if the
* SORT result is empty a new key is set and maybe the old content
+118 -42
View File
@@ -31,27 +31,56 @@ void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
}
}
/* Get the value from a hash identified by key. Returns either a string
* object or NULL if the value cannot be found. The refcount of the object
* is always increased by 1 when the value was found. */
robj *hashTypeGet(robj *o, robj *key) {
robj *value = NULL;
/* Get the value from a hash identified by key.
*
* If the string is found either REDIS_ENCODING_HT or REDIS_ENCODING_ZIPMAP
* is returned, and either **objval or **v and *vlen are set accordingly,
* so that objects in hash tables are returend as objects and pointers
* inside a zipmap are returned as such.
*
* If the object was not found -1 is returned.
*
* This function is copy on write friendly as there is no incr/decr
* of refcount needed if objects are accessed just for reading operations. */
int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v,
unsigned int *vlen)
{
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *v;
unsigned int vlen;
int found;
key = getDecodedObject(key);
if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) {
value = createStringObject((char*)v,vlen);
}
found = zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),v,vlen);
decrRefCount(key);
if (!found) return -1;
} else {
dictEntry *de = dictFind(o->ptr,key);
if (de != NULL) {
value = dictGetEntryVal(de);
incrRefCount(value);
}
if (de == NULL) return -1;
*objval = dictGetEntryVal(de);
}
return o->encoding;
}
/* Higher level function of hashTypeGet() that always returns a Redis
* object (either new or with refcount incremented), so that the caller
* can retain a reference or call decrRefCount after the usage.
*
* The lower level function can prevent copy on write so it is
* the preferred way of doing read operations. */
robj *hashTypeGetObject(robj *o, robj *key) {
robj *objval;
unsigned char *v;
unsigned int vlen;
int encoding = hashTypeGet(o,key,&objval,&v,&vlen);
switch(encoding) {
case REDIS_ENCODING_HT:
incrRefCount(objval);
return objval;
case REDIS_ENCODING_ZIPMAP:
objval = createStringObject((char*)v,vlen);
return objval;
default: return NULL;
}
return value;
}
/* Test if the key exists in the given hash. Returns 1 if the key
@@ -156,24 +185,50 @@ int hashTypeNext(hashTypeIterator *hi) {
}
/* Get key or value object at current iteration position.
* This increases the refcount of the field object by 1. */
robj *hashTypeCurrent(hashTypeIterator *hi, int what) {
robj *o;
* The returned item differs with the hash object encoding:
* - When encoding is REDIS_ENCODING_HT, the objval pointer is populated
* with the original object.
* - When encoding is REDIS_ENCODING_ZIPMAP, a pointer to the string and
* its length is retunred populating the v and vlen pointers.
* This function is copy on write friendly as accessing objects in read only
* does not require writing to any memory page.
*
* The function returns the encoding of the object, so that the caller
* can underestand if the key or value was returned as object or C string. */
int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen) {
if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
if (what & REDIS_HASH_KEY) {
o = createStringObject((char*)hi->zk,hi->zklen);
*v = hi->zk;
*vlen = hi->zklen;
} else {
o = createStringObject((char*)hi->zv,hi->zvlen);
*v = hi->zv;
*vlen = hi->zvlen;
}
} else {
if (what & REDIS_HASH_KEY) {
o = dictGetEntryKey(hi->de);
} else {
o = dictGetEntryVal(hi->de);
}
incrRefCount(o);
if (what & REDIS_HASH_KEY)
*objval = dictGetEntryKey(hi->de);
else
*objval = dictGetEntryVal(hi->de);
}
return hi->encoding;
}
/* A non copy-on-write friendly but higher level version of hashTypeCurrent()
* that always returns an object with refcount incremented by one (or a new
* object), so it's up to the caller to decrRefCount() the object if no
* reference is retained. */
robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
robj *obj;
unsigned char *v = NULL;
unsigned int vlen = 0;
int encoding = hashTypeCurrent(hi,what,&obj,&v,&vlen);
if (encoding == REDIS_ENCODING_HT) {
incrRefCount(obj);
return obj;
} else {
return createStringObject((char*)v,vlen);
}
return o;
}
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
@@ -270,7 +325,7 @@ void hincrbyCommand(redisClient *c) {
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
if ((current = hashTypeGet(o,c->argv[2])) != NULL) {
if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
if (getLongLongFromObjectOrReply(c,current,&value,
"hash value is not an integer") != REDIS_OK) {
decrRefCount(current);
@@ -293,20 +348,29 @@ void hincrbyCommand(redisClient *c) {
void hgetCommand(redisClient *c) {
robj *o, *value;
unsigned char *v;
unsigned int vlen;
int encoding;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
if ((value = hashTypeGet(o,c->argv[2])) != NULL) {
addReplyBulk(c,value);
decrRefCount(value);
if ((encoding = hashTypeGet(o,c->argv[2],&value,&v,&vlen)) != -1) {
if (encoding == REDIS_ENCODING_HT)
addReplyBulk(c,value);
else
addReplyBulkCBuffer(c,v,vlen);
} else {
addReply(c,shared.nullbulk);
}
}
void hmgetCommand(redisClient *c) {
int i;
int i, encoding;
robj *o, *value;
unsigned char *v;
unsigned int vlen;
o = lookupKeyRead(c->db,c->argv[1]);
if (o != NULL && o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
@@ -318,9 +382,12 @@ void hmgetCommand(redisClient *c) {
* an empty hash. The reply should then be a series of NULLs. */
addReplyMultiBulkLen(c,c->argc-2);
for (i = 2; i < c->argc; i++) {
if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) {
addReplyBulk(c,value);
decrRefCount(value);
if (o != NULL &&
(encoding = hashTypeGet(o,c->argv[i],&value,&v,&vlen)) != -1) {
if (encoding == REDIS_ENCODING_HT)
addReplyBulk(c,value);
else
addReplyBulkCBuffer(c,v,vlen);
} else {
addReply(c,shared.nullbulk);
}
@@ -351,7 +418,7 @@ void hlenCommand(redisClient *c) {
}
void genericHgetallCommand(redisClient *c, int flags) {
robj *o, *obj;
robj *o;
unsigned long count = 0;
hashTypeIterator *hi;
void *replylen = NULL;
@@ -362,16 +429,25 @@ void genericHgetallCommand(redisClient *c, int flags) {
replylen = addDeferredMultiBulkLength(c);
hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != REDIS_ERR) {
robj *obj;
unsigned char *v = NULL;
unsigned int vlen = 0;
int encoding;
if (flags & REDIS_HASH_KEY) {
obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
addReplyBulk(c,obj);
decrRefCount(obj);
encoding = hashTypeCurrent(hi,REDIS_HASH_KEY,&obj,&v,&vlen);
if (encoding == REDIS_ENCODING_HT)
addReplyBulk(c,obj);
else
addReplyBulkCBuffer(c,v,vlen);
count++;
}
if (flags & REDIS_HASH_VALUE) {
obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
addReplyBulk(c,obj);
decrRefCount(obj);
encoding = hashTypeCurrent(hi,REDIS_HASH_VALUE,&obj,&v,&vlen);
if (encoding == REDIS_ENCODING_HT)
addReplyBulk(c,obj);
else
addReplyBulkCBuffer(c,v,vlen);
count++;
}
}
+211 -77
View File
@@ -472,12 +472,11 @@ void rpopCommand(redisClient *c) {
}
void lrangeCommand(redisClient *c) {
robj *o, *value;
robj *o;
int start = atoi(c->argv[2]->ptr);
int end = atoi(c->argv[3]->ptr);
int llen;
int rangelen, j;
listTypeEntry entry;
int rangelen;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_LIST)) return;
@@ -499,14 +498,36 @@ void lrangeCommand(redisClient *c) {
/* Return the result in form of a multi-bulk reply */
addReplyMultiBulkLen(c,rangelen);
listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL);
for (j = 0; j < rangelen; j++) {
redisAssert(listTypeNext(li,&entry));
value = listTypeGet(&entry);
addReplyBulk(c,value);
decrRefCount(value);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p = ziplistIndex(o->ptr,start);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
while(rangelen--) {
ziplistGet(p,&vstr,&vlen,&vlong);
if (vstr) {
addReplyBulkCBuffer(c,vstr,vlen);
} else {
addReplyBulkLongLong(c,vlong);
}
p = ziplistNext(o->ptr,p);
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln;
/* If we are nearest to the end of the list, reach the element
* starting from tail and going backward, as it is faster. */
if (start > llen/2) start -= llen;
ln = listIndex(o->ptr,start);
while(rangelen--) {
addReplyBulk(c,ln->value);
ln = ln->next;
}
} else {
redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
}
listTypeReleaseIterator(li);
}
void ltrimCommand(redisClient *c) {
@@ -605,20 +626,58 @@ void lremCommand(redisClient *c) {
/* This is the semantic of this command:
* RPOPLPUSH srclist dstlist:
* IF LLEN(srclist) > 0
* element = RPOP srclist
* LPUSH dstlist element
* RETURN element
* ELSE
* RETURN nil
* END
* IF LLEN(srclist) > 0
* element = RPOP srclist
* LPUSH dstlist element
* RETURN element
* ELSE
* RETURN nil
* END
* END
*
* The idea is to be able to get an element from a list in a reliable way
* since the element is not just returned but pushed against another list
* as well. This command was originally proposed by Ezra Zygmuntowicz.
*/
void rpoplpushcommand(redisClient *c) {
void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
robj *aux;
if (!handleClientsWaitingListPush(origclient,dstkey,value)) {
/* Create the list if the key does not exist */
if (!dstobj) {
dstobj = createZiplistObject();
dbAdd(c->db,dstkey,dstobj);
} else {
touchWatchedKey(c->db,dstkey);
}
listTypePush(dstobj,value,REDIS_HEAD);
/* If we are pushing as a result of LPUSH against a key
* watched by BRPOPLPUSH, we need to rewrite the command vector
* as an LPUSH.
*
* If this is called directly by RPOPLPUSH (either directly
* or via a BRPOPLPUSH where the popped list exists)
* we should replicate the RPOPLPUSH command itself. */
if (c != origclient) {
aux = createStringObject("LPUSH",5);
rewriteClientCommandVector(origclient,3,aux,dstkey,value);
decrRefCount(aux);
} else {
/* Make sure to always use RPOPLPUSH in the replication / AOF,
* even if the original command was BRPOPLPUSH. */
aux = createStringObject("RPOPLPUSH",9);
rewriteClientCommandVector(origclient,3,aux,c->argv[1],c->argv[2]);
decrRefCount(aux);
}
server.dirty++;
}
/* Always send the pushed value to the client. */
addReplyBulk(c,value);
}
void rpoplpushCommand(redisClient *c) {
robj *sobj, *value;
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,sobj,REDIS_LIST)) return;
@@ -627,29 +686,22 @@ void rpoplpushcommand(redisClient *c) {
addReply(c,shared.nullbulk);
} else {
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
robj *touchedkey = c->argv[1];
if (dobj && checkType(c,dobj,REDIS_LIST)) return;
value = listTypePop(sobj,REDIS_TAIL);
/* Add the element to the target list (unless it's directly
* passed to some BLPOP-ing client */
if (!handleClientsWaitingListPush(c,c->argv[2],value)) {
/* Create the list if the key does not exist */
if (!dobj) {
dobj = createZiplistObject();
dbAdd(c->db,c->argv[2],dobj);
}
listTypePush(dobj,value,REDIS_HEAD);
}
/* Send the element to the client as reply as well */
addReplyBulk(c,value);
/* We saved touched key, and protect it, since rpoplpushHandlePush
* may change the client command argument vector. */
incrRefCount(touchedkey);
rpoplpushHandlePush(c,c,c->argv[2],dobj,value);
/* listTypePop returns an object with its refcount incremented */
decrRefCount(value);
/* Delete the source list when it is empty */
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
touchWatchedKey(c->db,touchedkey);
decrRefCount(touchedkey);
server.dirty++;
}
}
@@ -689,17 +741,23 @@ void rpoplpushcommand(redisClient *c) {
/* Set a client in blocking mode for the specified key, with the specified
* timeout */
void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout) {
void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) {
dictEntry *de;
list *l;
int j;
c->blocking_keys = zmalloc(sizeof(robj*)*numkeys);
c->blocking_keys_num = numkeys;
c->blockingto = timeout;
c->bpop.keys = zmalloc(sizeof(robj*)*numkeys);
c->bpop.count = numkeys;
c->bpop.timeout = timeout;
c->bpop.target = target;
if (target != NULL) {
incrRefCount(target);
}
for (j = 0; j < numkeys; j++) {
/* Add the key in the client structure, to map clients -> keys */
c->blocking_keys[j] = keys[j];
c->bpop.keys[j] = keys[j];
incrRefCount(keys[j]);
/* And in the other "side", to map keys -> clients */
@@ -719,7 +777,7 @@ void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout) {
}
/* Mark the client as a blocked client */
c->flags |= REDIS_BLOCKED;
server.blpop_blocked_clients++;
server.bpop_blocked_clients++;
}
/* Unblock a client that's waiting in a blocking operation such as BLPOP */
@@ -728,30 +786,29 @@ void unblockClientWaitingData(redisClient *c) {
list *l;
int j;
redisAssert(c->blocking_keys != NULL);
redisAssert(c->bpop.keys != NULL);
/* The client may wait for multiple keys, so unblock it for every key. */
for (j = 0; j < c->blocking_keys_num; j++) {
for (j = 0; j < c->bpop.count; j++) {
/* Remove this client from the list of clients waiting for this key. */
de = dictFind(c->db->blocking_keys,c->blocking_keys[j]);
de = dictFind(c->db->blocking_keys,c->bpop.keys[j]);
redisAssert(de != NULL);
l = dictGetEntryVal(de);
listDelNode(l,listSearchKey(l,c));
/* If the list is empty we need to remove it to avoid wasting memory */
if (listLength(l) == 0)
dictDelete(c->db->blocking_keys,c->blocking_keys[j]);
decrRefCount(c->blocking_keys[j]);
dictDelete(c->db->blocking_keys,c->bpop.keys[j]);
decrRefCount(c->bpop.keys[j]);
}
/* Cleanup the client structure */
zfree(c->blocking_keys);
c->blocking_keys = NULL;
c->flags &= (~REDIS_BLOCKED);
server.blpop_blocked_clients--;
/* We want to process data if there is some command waiting
* in the input buffer. Note that this is safe even if
* unblockClientWaitingData() gets called from freeClient() because
* freeClient() will be smart enough to call this function
* *after* c->querybuf was set to NULL. */
if (c->querybuf && sdslen(c->querybuf) > 0) processInputBuffer(c);
zfree(c->bpop.keys);
c->bpop.keys = NULL;
if (c->bpop.target) decrRefCount(c->bpop.target);
c->bpop.target = NULL;
c->flags &= ~REDIS_BLOCKED;
c->flags |= REDIS_UNBLOCKED;
server.bpop_blocked_clients--;
listAddNodeTail(server.unblocked_clients,c);
}
/* This should be called from any function PUSHing into lists.
@@ -767,39 +824,82 @@ void unblockClientWaitingData(redisClient *c) {
int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
struct dictEntry *de;
redisClient *receiver;
list *l;
int numclients;
list *clients;
listNode *ln;
robj *dstkey, *dstobj;
de = dictFind(c->db->blocking_keys,key);
if (de == NULL) return 0;
l = dictGetEntryVal(de);
ln = listFirst(l);
redisAssert(ln != NULL);
receiver = ln->value;
clients = dictGetEntryVal(de);
numclients = listLength(clients);
addReplyMultiBulkLen(receiver,2);
addReplyBulk(receiver,key);
addReplyBulk(receiver,ele);
unblockClientWaitingData(receiver);
return 1;
/* Try to handle the push as long as there are clients waiting for a push.
* Note that "numclients" is used because the list of clients waiting for a
* push on "key" is deleted by unblockClient() when empty.
*
* This loop will have more than 1 iteration when there is a BRPOPLPUSH
* that cannot push the target list because it does not contain a list. If
* this happens, it simply tries the next client waiting for a push. */
while (numclients--) {
ln = listFirst(clients);
redisAssert(ln != NULL);
receiver = ln->value;
dstkey = receiver->bpop.target;
/* Protect receiver->bpop.target, that will be freed by
* the next unblockClientWaitingData() call. */
if (dstkey) incrRefCount(dstkey);
/* This should remove the first element of the "clients" list. */
unblockClientWaitingData(receiver);
if (dstkey == NULL) {
/* BRPOP/BLPOP */
addReplyMultiBulkLen(receiver,2);
addReplyBulk(receiver,key);
addReplyBulk(receiver,ele);
return 1; /* Serve just the first client as in B[RL]POP semantics */
} else {
/* BRPOPLPUSH, note that receiver->db is always equal to c->db. */
dstobj = lookupKeyWrite(receiver->db,dstkey);
if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) {
rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele);
decrRefCount(dstkey);
return 1;
}
decrRefCount(dstkey);
}
}
return 0;
}
int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) {
long tval;
if (getLongFromObjectOrReply(c,object,&tval,
"timeout is not an integer or out of range") != REDIS_OK)
return REDIS_ERR;
if (tval < 0) {
addReplyError(c,"timeout is negative");
return REDIS_ERR;
}
if (tval > 0) tval += time(NULL);
*timeout = tval;
return REDIS_OK;
}
/* Blocking RPOP/LPOP */
void blockingPopGenericCommand(redisClient *c, int where) {
robj *o;
long long lltimeout;
time_t timeout;
int j;
/* Make sure timeout is an integer value */
if (getLongLongFromObjectOrReply(c,c->argv[c->argc-1],&lltimeout,
"timeout is not an integer") != REDIS_OK) return;
/* Make sure the timeout is not negative */
if (lltimeout < 0) {
addReplyError(c,"timeout is negative");
if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout) != REDIS_OK)
return;
}
for (j = 1; j < c->argc-1; j++) {
o = lookupKeyWrite(c->db,c->argv[j]);
@@ -811,6 +911,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
if (listTypeLength(o) != 0) {
/* If the list contains elements fall back to the usual
* non-blocking POP operation */
struct redisCommand *orig_cmd;
robj *argv[2], **orig_argv;
int orig_argc;
@@ -818,6 +919,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
* popGenericCommand() as the command takes a single key. */
orig_argv = c->argv;
orig_argc = c->argc;
orig_cmd = c->cmd;
argv[1] = c->argv[j];
c->argv = argv;
c->argc = 2;
@@ -829,11 +931,14 @@ void blockingPopGenericCommand(redisClient *c, int where) {
* because it is... */
addReplyMultiBulkLen(c,2);
addReplyBulk(c,argv[1]);
popGenericCommand(c,where);
/* Fix the client structure with the original stuff */
c->argv = orig_argv;
c->argc = orig_argc;
c->cmd = orig_cmd;
return;
}
}
@@ -848,9 +953,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
}
/* If the list is empty or the key does not exists we must block */
timeout = lltimeout;
if (timeout > 0) timeout += time(NULL);
blockForKeys(c,c->argv+1,c->argc-2,timeout);
blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
}
void blpopCommand(redisClient *c) {
@@ -860,3 +963,34 @@ void blpopCommand(redisClient *c) {
void brpopCommand(redisClient *c) {
blockingPopGenericCommand(c,REDIS_TAIL);
}
void brpoplpushCommand(redisClient *c) {
time_t timeout;
if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK)
return;
robj *key = lookupKeyWrite(c->db, c->argv[1]);
if (key == NULL) {
if (c->flags & REDIS_MULTI) {
/* Blocking against an empty list in a multi state
* returns immediately. */
addReply(c, shared.nullbulk);
} else {
/* The list is empty and the client blocks. */
blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]);
}
} else {
if (key->type != REDIS_LIST) {
addReply(c, shared.wrongtypeerr);
} else {
/* The list exists and has elements, so
* the regular rpoplpushCommand is executed. */
redisAssert(listTypeLength(key) > 0);
rpoplpushCommand(c);
}
}
}
+160 -64
View File
@@ -47,17 +47,17 @@ int setTypeAdd(robj *subject, robj *value) {
return 0;
}
int setTypeRemove(robj *subject, robj *value) {
int setTypeRemove(robj *setobj, robj *value) {
long long llval;
if (subject->encoding == REDIS_ENCODING_HT) {
if (dictDelete(subject->ptr,value) == DICT_OK) {
if (htNeedsResize(subject->ptr)) dictResize(subject->ptr);
if (setobj->encoding == REDIS_ENCODING_HT) {
if (dictDelete(setobj->ptr,value) == DICT_OK) {
if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr);
return 1;
}
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
} else if (setobj->encoding == REDIS_ENCODING_INTSET) {
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
uint8_t success;
subject->ptr = intsetRemove(subject->ptr,llval,&success);
int success;
setobj->ptr = intsetRemove(setobj->ptr,llval,&success);
if (success) return 1;
}
} else {
@@ -101,40 +101,77 @@ void setTypeReleaseIterator(setTypeIterator *si) {
}
/* Move to the next entry in the set. Returns the object at the current
* position, or NULL when the end is reached. This object will have its
* refcount incremented, so the caller needs to take care of this. */
robj *setTypeNext(setTypeIterator *si) {
robj *ret = NULL;
* position.
*
* Since set elements can be internally be stored as redis objects or
* simple arrays of integers, setTypeNext returns the encoding of the
* set object you are iterating, and will populate the appropriate pointer
* (eobj) or (llobj) accordingly.
*
* When there are no longer elements -1 is returned.
* Returned objects ref count is not incremented, so this function is
* copy on write friendly. */
int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
if (si->encoding == REDIS_ENCODING_HT) {
dictEntry *de = dictNext(si->di);
if (de != NULL) {
ret = dictGetEntryKey(de);
incrRefCount(ret);
}
if (de == NULL) return -1;
*objele = dictGetEntryKey(de);
} else if (si->encoding == REDIS_ENCODING_INTSET) {
int64_t llval;
if (intsetGet(si->subject->ptr,si->ii++,&llval))
ret = createStringObjectFromLongLong(llval);
if (!intsetGet(si->subject->ptr,si->ii++,llele))
return -1;
}
return ret;
return si->encoding;
}
/* The not copy on write friendly version but easy to use version
* of setTypeNext() is setTypeNextObject(), returning new objects
* or incrementing the ref count of returned objects. So if you don't
* retain a pointer to this object you should call decrRefCount() against it.
*
* This function is the way to go for write operations where COW is not
* an issue as the result will be anyway of incrementing the ref count. */
robj *setTypeNextObject(setTypeIterator *si) {
int64_t intele;
robj *objele;
int encoding;
/* Return random element from set. The returned object will always have
* an incremented refcount. */
robj *setTypeRandomElement(robj *subject) {
robj *ret = NULL;
if (subject->encoding == REDIS_ENCODING_HT) {
dictEntry *de = dictGetRandomKey(subject->ptr);
ret = dictGetEntryKey(de);
incrRefCount(ret);
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
long long llval = intsetRandom(subject->ptr);
ret = createStringObjectFromLongLong(llval);
encoding = setTypeNext(si,&objele,&intele);
switch(encoding) {
case -1: return NULL;
case REDIS_ENCODING_INTSET:
return createStringObjectFromLongLong(intele);
case REDIS_ENCODING_HT:
incrRefCount(objele);
return objele;
default:
redisPanic("Unsupported encoding");
}
return NULL; /* just to suppress warnings */
}
/* Return random element from a non empty set.
* The returned element can be a int64_t value if the set is encoded
* as an "intset" blob of integers, or a redis object if the set
* is a regular set.
*
* The caller provides both pointers to be populated with the right
* object. The return value of the function is the object->encoding
* field of the object and is used by the caller to check if the
* int64_t pointer or the redis object pointere was populated.
*
* When an object is returned (the set was a real set) the ref count
* of the object is not incremented so this function can be considered
* copy on write friendly. */
int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
if (setobj->encoding == REDIS_ENCODING_HT) {
dictEntry *de = dictGetRandomKey(setobj->ptr);
*objele = dictGetEntryKey(de);
} else if (setobj->encoding == REDIS_ENCODING_INTSET) {
*llele = intsetRandom(setobj->ptr);
} else {
redisPanic("Unknown set encoding");
}
return ret;
return setobj->encoding;
}
unsigned long setTypeSize(robj *subject) {
@@ -150,25 +187,30 @@ unsigned long setTypeSize(robj *subject) {
/* Convert the set to specified encoding. The resulting dict (when converting
* to a hashtable) is presized to hold the number of elements in the original
* set. */
void setTypeConvert(robj *subject, int enc) {
void setTypeConvert(robj *setobj, int enc) {
setTypeIterator *si;
robj *element;
redisAssert(subject->type == REDIS_SET);
redisAssert(setobj->type == REDIS_SET &&
setobj->encoding == REDIS_ENCODING_INTSET);
if (enc == REDIS_ENCODING_HT) {
int64_t intele;
dict *d = dictCreate(&setDictType,NULL);
/* Presize the dict to avoid rehashing */
dictExpand(d,intsetLen(subject->ptr));
robj *element;
/* setTypeGet returns a robj with incremented refcount */
si = setTypeInitIterator(subject);
while ((element = setTypeNext(si)) != NULL)
/* Presize the dict to avoid rehashing */
dictExpand(d,intsetLen(setobj->ptr));
/* To add the elements we extract integers and create redis objects */
si = setTypeInitIterator(setobj);
while (setTypeNext(si,NULL,&intele) != -1) {
element = createStringObjectFromLongLong(intele);
redisAssert(dictAdd(d,element,NULL) == DICT_OK);
}
setTypeReleaseIterator(si);
subject->encoding = REDIS_ENCODING_HT;
zfree(subject->ptr);
subject->ptr = d;
setobj->encoding = REDIS_ENCODING_HT;
zfree(setobj->ptr);
setobj->ptr = d;
} else {
redisPanic("Unsupported set conversion");
}
@@ -283,36 +325,47 @@ void scardCommand(redisClient *c) {
}
void spopCommand(redisClient *c) {
robj *set, *ele;
robj *set, *ele, *aux;
int64_t llele;
int encoding;
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
ele = setTypeRandomElement(set);
if (ele == NULL) {
addReply(c,shared.nullbulk);
encoding = setTypeRandomElement(set,&ele,&llele);
if (encoding == REDIS_ENCODING_INTSET) {
ele = createStringObjectFromLongLong(llele);
set->ptr = intsetRemove(set->ptr,llele,NULL);
} else {
incrRefCount(ele);
setTypeRemove(set,ele);
addReplyBulk(c,ele);
decrRefCount(ele);
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
/* Replicate/AOF this command as an SREM operation */
aux = createStringObject("SREM",4);
rewriteClientCommandVector(c,3,aux,c->argv[1],ele);
decrRefCount(ele);
decrRefCount(aux);
addReplyBulk(c,ele);
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
void srandmemberCommand(redisClient *c) {
robj *set, *ele;
int64_t llele;
int encoding;
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
ele = setTypeRandomElement(set);
if (ele == NULL) {
addReply(c,shared.nullbulk);
encoding = setTypeRandomElement(set,&ele,&llele);
if (encoding == REDIS_ENCODING_INTSET) {
addReplyBulkLongLong(c,llele);
} else {
addReplyBulk(c,ele);
decrRefCount(ele);
}
}
@@ -323,9 +376,11 @@ int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si;
robj *ele, *dstset = NULL;
robj *eleobj, *dstset = NULL;
int64_t intobj;
void *replylen = NULL;
unsigned long j, cardinality = 0;
int encoding;
for (j = 0; j < setnum; j++) {
robj *setobj = dstkey ?
@@ -371,20 +426,61 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
* the element against all the other sets, if at least one set does
* not include the element it is discarded */
si = setTypeInitIterator(sets[0]);
while((ele = setTypeNext(si)) != NULL) {
for (j = 1; j < setnum; j++)
if (!setTypeIsMember(sets[j],ele)) break;
while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
for (j = 1; j < setnum; j++) {
if (sets[j] == sets[0]) continue;
if (encoding == REDIS_ENCODING_INTSET) {
/* intset with intset is simple... and fast */
if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
!intsetFind((intset*)sets[j]->ptr,intobj))
{
break;
/* in order to compare an integer with an object we
* have to use the generic function, creating an object
* for this */
} else if (sets[j]->encoding == REDIS_ENCODING_HT) {
eleobj = createStringObjectFromLongLong(intobj);
if (!setTypeIsMember(sets[j],eleobj)) {
decrRefCount(eleobj);
break;
}
decrRefCount(eleobj);
}
} else if (encoding == REDIS_ENCODING_HT) {
/* Optimization... if the source object is integer
* encoded AND the target set is an intset, we can get
* a much faster path. */
if (eleobj->encoding == REDIS_ENCODING_INT &&
sets[j]->encoding == REDIS_ENCODING_INTSET &&
!intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
{
break;
/* else... object to object check is easy as we use the
* type agnostic API here. */
} else if (!setTypeIsMember(sets[j],eleobj)) {
break;
}
}
}
/* Only take action when all sets contain the member */
if (j == setnum) {
if (!dstkey) {
addReplyBulk(c,ele);
if (encoding == REDIS_ENCODING_HT)
addReplyBulk(c,eleobj);
else
addReplyBulkLongLong(c,intobj);
cardinality++;
} else {
setTypeAdd(dstset,ele);
if (encoding == REDIS_ENCODING_INTSET) {
eleobj = createStringObjectFromLongLong(intobj);
setTypeAdd(dstset,eleobj);
decrRefCount(eleobj);
} else {
setTypeAdd(dstset,eleobj);
}
}
}
decrRefCount(ele);
}
setTypeReleaseIterator(si);
@@ -452,7 +548,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
if (!sets[j]) continue; /* non existing keys are like empty sets */
si = setTypeInitIterator(sets[j]);
while((ele = setTypeNext(si)) != NULL) {
while((ele = setTypeNextObject(si)) != NULL) {
if (op == REDIS_OP_UNION || j == 0) {
if (setTypeAdd(dstset,ele)) {
cardinality++;
@@ -474,7 +570,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
if (!dstkey) {
addReplyMultiBulkLen(c,cardinality);
si = setTypeInitIterator(dstset);
while((ele = setTypeNext(si)) != NULL) {
while((ele = setTypeNextObject(si)) != NULL) {
addReplyBulk(c,ele);
decrRefCount(ele);
}
+231 -61
View File
@@ -4,6 +4,14 @@
* String Commands
*----------------------------------------------------------------------------*/
static int checkStringLength(redisClient *c, long long size) {
if (size > 512*1024*1024) {
addReplyError(c,"string exceeds maximum allowed size (512MB)");
return REDIS_ERR;
}
return REDIS_OK;
}
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
int retval;
long seconds = 0; /* initialized to avoid an harmness warning */
@@ -17,6 +25,7 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
}
}
lookupKeyWrite(c->db,key); /* Force expire of old key if needed */
retval = dbAdd(c->db,key,val);
if (retval == REDIS_ERR) {
if (!nx) {
@@ -80,6 +89,204 @@ void getsetCommand(redisClient *c) {
removeExpire(c->db,c->argv[1]);
}
static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
long long loffset;
char *err = "bit offset is not an integer or out of range";
if (getLongLongFromObjectOrReply(c,o,&loffset,err) != REDIS_OK)
return REDIS_ERR;
/* Limit offset to 512MB in bytes */
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
{
addReplyError(c,err);
return REDIS_ERR;
}
*offset = (size_t)loffset;
return REDIS_OK;
}
void setbitCommand(redisClient *c) {
robj *o;
char *err = "bit is not an integer or out of range";
size_t bitoffset;
int byte, bit;
int byteval, bitval;
long on;
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
return;
if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK)
return;
/* Bits can only be set or cleared... */
if (on & ~1) {
addReplyError(c,err);
return;
}
o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) {
o = createObject(REDIS_STRING,sdsempty());
dbAdd(c->db,c->argv[1],o);
} else {
if (checkType(c,o,REDIS_STRING)) return;
/* Create a copy when the object is shared or encoded. */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplace(c->db,c->argv[1],o);
}
}
/* Grow sds value to the right length if necessary */
byte = bitoffset >> 3;
o->ptr = sdsgrowzero(o->ptr,byte+1);
/* Get current values */
byteval = ((char*)o->ptr)[byte];
bit = 7 - (bitoffset & 0x7);
bitval = byteval & (1 << bit);
/* Update byte with new bit value and return original value */
byteval &= ~(1 << bit);
byteval |= ((on & 0x1) << bit);
((char*)o->ptr)[byte] = byteval;
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c, bitval ? shared.cone : shared.czero);
}
void getbitCommand(redisClient *c) {
robj *o;
char llbuf[32];
size_t bitoffset;
size_t byte, bit;
size_t bitval = 0;
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
byte = bitoffset >> 3;
bit = 7 - (bitoffset & 0x7);
if (o->encoding != REDIS_ENCODING_RAW) {
if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
bitval = llbuf[byte] & (1 << bit);
} else {
if (byte < sdslen(o->ptr))
bitval = ((char*)o->ptr)[byte] & (1 << bit);
}
addReply(c, bitval ? shared.cone : shared.czero);
}
void setrangeCommand(redisClient *c) {
robj *o;
long offset;
sds value = c->argv[3]->ptr;
if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
return;
if (offset < 0) {
addReplyError(c,"offset is out of range");
return;
}
o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) {
/* Return 0 when setting nothing on a non-existing string */
if (sdslen(value) == 0) {
addReply(c,shared.czero);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
return;
o = createObject(REDIS_STRING,sdsempty());
dbAdd(c->db,c->argv[1],o);
} else {
size_t olen;
/* Key exists, check type */
if (checkType(c,o,REDIS_STRING))
return;
/* Return existing string length when setting nothing */
olen = stringObjectLen(o);
if (sdslen(value) == 0) {
addReplyLongLong(c,olen);
return;
}
/* Return when the resulting string exceeds allowed size */
if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
return;
/* Create a copy when the object is shared or encoded. */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplace(c->db,c->argv[1],o);
}
}
if (sdslen(value) > 0) {
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
memcpy((char*)o->ptr+offset,value,sdslen(value));
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
addReplyLongLong(c,sdslen(o->ptr));
}
void getrangeCommand(redisClient *c) {
robj *o;
long start, end;
char *str, llbuf[32];
size_t strlen;
if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
return;
if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
if (o->encoding == REDIS_ENCODING_INT) {
str = llbuf;
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
} else {
str = o->ptr;
strlen = sdslen(str);
}
/* Convert negative indexes */
if (start < 0) start = strlen+start;
if (end < 0) end = strlen+end;
if (start < 0) start = 0;
if (end < 0) end = 0;
if ((unsigned)end >= strlen) end = strlen-1;
/* Precondition: end >= 0 && end < strlen, so the only condition where
* nothing can be returned is: start > end. */
if (start > end) {
addReply(c,shared.emptybulk);
} else {
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
}
}
void mgetCommand(redisClient *c) {
int j;
@@ -139,14 +346,19 @@ void msetnxCommand(redisClient *c) {
}
void incrDecrCommand(redisClient *c, long long incr) {
long long value;
long long value, oldvalue;
robj *o;
o = lookupKeyWrite(c->db,c->argv[1]);
if (o != NULL && checkType(c,o,REDIS_STRING)) return;
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
oldvalue = value;
value += incr;
if ((incr < 0 && value > oldvalue) || (incr > 0 && value < oldvalue)) {
addReplyError(c,"increment or decrement would overflow");
return;
}
o = createStringObjectFromLongLong(value);
dbReplace(c->db,c->argv[1],o);
touchWatchedKey(c->db,c->argv[1]);
@@ -179,39 +391,37 @@ void decrbyCommand(redisClient *c) {
}
void appendCommand(redisClient *c) {
int retval;
size_t totlen;
robj *o;
robj *o, *append;
o = lookupKeyWrite(c->db,c->argv[1]);
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (o == NULL) {
/* Create the key */
retval = dbAdd(c->db,c->argv[1],c->argv[2]);
c->argv[2] = tryObjectEncoding(c->argv[2]);
dbAdd(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]);
totlen = stringObjectLen(c->argv[2]);
} else {
if (o->type != REDIS_STRING) {
addReply(c,shared.wrongtypeerr);
/* Key exists, check type */
if (checkType(c,o,REDIS_STRING))
return;
}
/* If the object is specially encoded or shared we have to make
* a copy */
/* "append" is an argument, so always an sds */
append = c->argv[2];
totlen = stringObjectLen(o)+sdslen(append->ptr);
if (checkStringLength(c,totlen) != REDIS_OK)
return;
/* If the object is shared or encoded, we have to make a copy */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplace(c->db,c->argv[1],o);
}
/* APPEND! */
if (c->argv[2]->encoding == REDIS_ENCODING_RAW) {
o->ptr = sdscatlen(o->ptr,
c->argv[2]->ptr, sdslen(c->argv[2]->ptr));
} else {
o->ptr = sdscatprintf(o->ptr, "%ld",
(unsigned long) c->argv[2]->ptr);
}
/* Append the value */
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
totlen = sdslen(o->ptr);
}
touchWatchedKey(c->db,c->argv[1]);
@@ -219,50 +429,10 @@ void appendCommand(redisClient *c) {
addReplyLongLong(c,totlen);
}
void substrCommand(redisClient *c) {
robj *o;
long start = atoi(c->argv[2]->ptr);
long end = atoi(c->argv[3]->ptr);
size_t rangelen, strlen;
sds range;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
o = getDecodedObject(o);
strlen = sdslen(o->ptr);
/* convert negative indexes */
if (start < 0) start = strlen+start;
if (end < 0) end = strlen+end;
if (start < 0) start = 0;
if (end < 0) end = 0;
/* indexes sanity checks */
if (start > end || (size_t)start >= strlen) {
/* Out of range start or start > end result in null reply */
addReply(c,shared.nullbulk);
decrRefCount(o);
return;
}
if ((size_t)end >= strlen) end = strlen-1;
rangelen = (end-start)+1;
/* Return the result */
addReplySds(c,sdscatprintf(sdsempty(),"$%zu\r\n",rangelen));
range = sdsnewlen((char*)o->ptr+start,rangelen);
addReplySds(c,range);
addReply(c,shared.crlf);
decrRefCount(o);
}
void strlenCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
o = getDecodedObject(o);
addReplyLongLong(c,sdslen(o->ptr));
decrRefCount(o);
addReplyLongLong(c,stringObjectLen(o));
}
+33 -9
View File
@@ -263,7 +263,7 @@ zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
* Returns 0 when the element cannot be found, rank otherwise.
* Note that the rank is 1-based due to the span of zsl->header to the
* first element. */
unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) {
zskiplistNode *x;
unsigned long rank = 0;
int i;
@@ -287,7 +287,7 @@ unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
}
/* Finds an element by its rank. The rank argument needs to be 1-based. */
zskiplistNode* zslistTypeGetElementByRank(zskiplist *zsl, unsigned long rank) {
zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
zskiplistNode *x;
unsigned long traversed = 0;
int i;
@@ -615,6 +615,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
if (obj->type == REDIS_ZSET) {
src[i].dict = ((zset*)obj->ptr)->dict;
} else if (obj->type == REDIS_SET) {
if (obj->encoding == REDIS_ENCODING_INTSET)
setTypeConvert(obj, REDIS_ENCODING_HT);
redisAssert(obj->encoding == REDIS_ENCODING_HT);
src[i].dict = (obj->ptr);
} else {
zfree(src);
@@ -682,7 +686,18 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
score = src[0].weight * zunionInterDictValue(de);
for (j = 1; j < setnum; j++) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
dictEntry *other;
/* If it's the same dictionary don't lookup as we are not
* in the context of a safe iterator. It's the same
* dictionary so we are sure the element is inside.
* This happens on SINTERSTORE dest 2 mykey mykey. */
if (src[j].dict == src[0].dict) {
other = de;
} else {
other = dictFind(src[j].dict,dictGetEntryKey(de));
}
if (other) {
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(&score,value,aggregate);
@@ -720,10 +735,19 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
/* because the zsets are sorted by size, its only possible
* for sets at larger indices to hold this entry */
for (j = (i+1); j < setnum; j++) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
if (other) {
value = src[j].weight * zunionInterDictValue(other);
/* It is not safe to access the zset we are
* iterating, so explicitly check for equal object. */
if (src[j].dict == src[i].dict) {
value = src[i].weight * zunionInterDictValue(de);
zunionInterAggregate(&score,value,aggregate);
} else {
dictEntry *other;
other = dictFind(src[j].dict,dictGetEntryKey(de));
if (other) {
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(&score,value,aggregate);
}
}
}
@@ -810,10 +834,10 @@ void zrangeGenericCommand(redisClient *c, int reverse) {
/* check if starting point is trivial, before searching
* the element in log(N) time */
if (reverse) {
ln = start == 0 ? zsl->tail : zslistTypeGetElementByRank(zsl, llen-start);
ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start);
} else {
ln = start == 0 ?
zsl->header->level[0].forward : zslistTypeGetElementByRank(zsl, start+1);
zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1);
}
/* Return the result in form of a multi-bulk reply */
@@ -1039,7 +1063,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
}
score = dictGetEntryVal(de);
rank = zslistTypeGetRank(zsl, *score, c->argv[2]);
rank = zslGetRank(zsl, *score, c->argv[2]);
if (rank) {
if (reverse) {
addReplyLongLong(c, zsl->length - rank);
+12
View File
@@ -1,6 +1,7 @@
#include "redis.h"
#include <ctype.h>
#include <limits.h>
#include <sys/time.h>
/* Glob-style pattern matching. */
int stringmatchlen(const char *pattern, int patternLen,
@@ -241,3 +242,14 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llongval) {
return isStringRepresentableAsLongLong(o->ptr,llongval);
}
}
/* Return the UNIX time in microseconds */
long long ustime(void) {
struct timeval tv;
long long ust;
gettimeofday(&tv, NULL);
ust = ((long long)tv.tv_sec)*1000000;
ust += tv.tv_usec;
return ust;
}
+5
View File
@@ -0,0 +1,5 @@
{
<lzf_unitialized_hash_table>
Memcheck:Cond
fun:lzf_compress
}
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.1.7"
#define REDIS_VERSION "2.2.15"
+4 -4
View File
@@ -1064,11 +1064,11 @@ void execBlockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd, int
*
* Return 1 if the client is marked as blocked, 0 if the client can
* continue as the keys it is going to access appear to be in memory. */
int blockClientOnSwappedKeys(redisClient *c, struct redisCommand *cmd) {
if (cmd->vm_preload_proc != NULL) {
cmd->vm_preload_proc(c,cmd,c->argc,c->argv);
int blockClientOnSwappedKeys(redisClient *c) {
if (c->cmd->vm_preload_proc != NULL) {
c->cmd->vm_preload_proc(c,c->cmd,c->argc,c->argv);
} else {
waitForMultipleSwappedKeys(c,cmd,c->argc,c->argv);
waitForMultipleSwappedKeys(c,c->cmd,c->argc,c->argv);
}
/* If the client was blocked for at least one key, mark it as blocked. */
+44 -34
View File
@@ -119,6 +119,7 @@ static unsigned int zipEntryEncoding(unsigned char *p) {
return p[0] & 0xf0;
}
assert(NULL);
return 0;
}
/* Return bytes needed to store integer encoded by 'encoding' */
@@ -129,13 +130,14 @@ static unsigned int zipIntSize(unsigned char encoding) {
case ZIP_INT_64B: return sizeof(int64_t);
}
assert(NULL);
return 0;
}
/* Decode the encoded length pointed by 'p'. If a pointer to 'lensize' is
* provided, it is set to the number of bytes required to encode the length. */
static unsigned int zipDecodeLength(unsigned char *p, unsigned int *lensize) {
unsigned char encoding = zipEntryEncoding(p);
unsigned int len;
unsigned int len = 0;
if (ZIP_IS_STR(encoding)) {
switch(encoding) {
@@ -300,7 +302,7 @@ static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encodi
static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
int16_t i16;
int32_t i32;
int64_t i64, ret;
int64_t i64, ret = 0;
if (encoding == ZIP_INT_16B) {
memcpy(&i16,p,sizeof(i16));
ret = i16;
@@ -396,12 +398,17 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
offset = p-zl;
extra = rawlensize-next.prevrawlensize;
zl = ziplistResize(zl,curlen+extra);
ZIPLIST_TAIL_OFFSET(zl) += extra;
p = zl+offset;
/* Move the tail to the back. */
/* Current pointer and offset for next element. */
np = p+rawlen;
noffset = np-zl;
/* Update tail offset when next element is not the tail element. */
if ((zl+ZIPLIST_TAIL_OFFSET(zl)) != np)
ZIPLIST_TAIL_OFFSET(zl) += extra;
/* Move the tail to the back. */
memmove(np+rawlensize,
np+next.prevrawlensize,
curlen-noffset-next.prevrawlensize-1);
@@ -595,7 +602,12 @@ unsigned char *ziplistIndex(unsigned char *zl, int index) {
return (p[0] == ZIP_END || index > 0) ? NULL : p;
}
/* Return pointer to next entry in ziplist. */
/* Return pointer to next entry in ziplist.
*
* zl is the pointer to the ziplist
* p is the pointer to the current element
*
* The element after 'p' is returned, otherwise NULL if we are at the end. */
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
((void) zl);
@@ -870,7 +882,7 @@ void pop(unsigned char *zl, int where) {
}
}
void randstring(char *target, unsigned int min, unsigned int max) {
int randstring(char *target, unsigned int min, unsigned int max) {
int p, len = min+rand()%(max-min+1);
int minval, maxval;
switch(rand() % 3) {
@@ -892,10 +904,9 @@ void randstring(char *target, unsigned int min, unsigned int max) {
while(p < len)
target[p++] = minval+rand()%(maxval-minval+1);
return;
return len;
}
int main(int argc, char **argv) {
unsigned char *zl, *p;
unsigned char *entry;
@@ -1228,6 +1239,7 @@ int main(int argc, char **argv) {
int i,j,len,where;
unsigned char *p;
char buf[1024];
int buflen;
list *ref;
listNode *refnode;
@@ -1236,10 +1248,6 @@ int main(int argc, char **argv) {
unsigned int slen;
long long sval;
/* In the regression for the cascade bug, it was triggered
* with a random seed of 2. */
srand(2);
for (i = 0; i < 20000; i++) {
zl = ziplistNew();
ref = listCreate();
@@ -1249,31 +1257,32 @@ int main(int argc, char **argv) {
/* Create lists */
for (j = 0; j < len; j++) {
where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
switch(rand() % 4) {
case 0:
sprintf(buf,"%lld",(0LL + rand()) >> 20);
break;
case 1:
sprintf(buf,"%lld",(0LL + rand()));
break;
case 2:
sprintf(buf,"%lld",(0LL + rand()) << 20);
break;
case 3:
randstring(buf,0,256);
break;
default:
assert(NULL);
if (rand() % 2) {
buflen = randstring(buf,1,sizeof(buf)-1);
} else {
switch(rand() % 3) {
case 0:
buflen = sprintf(buf,"%lld",(0LL + rand()) >> 20);
break;
case 1:
buflen = sprintf(buf,"%lld",(0LL + rand()));
break;
case 2:
buflen = sprintf(buf,"%lld",(0LL + rand()) << 20);
break;
default:
assert(NULL);
}
}
/* Add to ziplist */
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), where);
zl = ziplistPush(zl, (unsigned char*)buf, buflen, where);
/* Add to reference list */
if (where == ZIPLIST_HEAD) {
listAddNodeHead(ref,sdsnew(buf));
listAddNodeHead(ref,sdsnewlen(buf, buflen));
} else if (where == ZIPLIST_TAIL) {
listAddNodeTail(ref,sdsnew(buf));
listAddNodeTail(ref,sdsnewlen(buf, buflen));
} else {
assert(NULL);
}
@@ -1288,12 +1297,13 @@ int main(int argc, char **argv) {
assert(ziplistGet(p,&sstr,&slen,&sval));
if (sstr == NULL) {
sprintf(buf,"%lld",sval);
buflen = sprintf(buf,"%lld",sval);
} else {
memcpy(buf,sstr,slen);
buf[slen] = '\0';
buflen = slen;
memcpy(buf,sstr,buflen);
buf[buflen] = '\0';
}
assert(strcmp(buf,listNodeValue(refnode)) == 0);
assert(memcmp(buf,listNodeValue(refnode),buflen) == 0);
}
zfree(zl);
listRelease(ref);
+14 -13
View File
@@ -33,6 +33,7 @@
#include <string.h>
#include <pthread.h>
#include "config.h"
#include "zmalloc.h"
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
@@ -52,7 +53,7 @@
#define free(ptr) tc_free(ptr)
#endif
#define increment_used_memory(__n) do { \
#define update_zmalloc_stat_alloc(__n,__size) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
if (zmalloc_thread_safe) { \
@@ -64,7 +65,7 @@
} \
} while(0)
#define decrement_used_memory(__n) do { \
#define update_zmalloc_stat_free(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
if (zmalloc_thread_safe) { \
@@ -92,11 +93,11 @@ void *zmalloc(size_t size) {
if (!ptr) zmalloc_oom(size);
#ifdef HAVE_MALLOC_SIZE
increment_used_memory(redis_malloc_size(ptr));
update_zmalloc_stat_alloc(redis_malloc_size(ptr),size);
return ptr;
#else
*((size_t*)ptr) = size;
increment_used_memory(size+PREFIX_SIZE);
update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);
return (char*)ptr+PREFIX_SIZE;
#endif
}
@@ -106,11 +107,11 @@ void *zcalloc(size_t size) {
if (!ptr) zmalloc_oom(size);
#ifdef HAVE_MALLOC_SIZE
increment_used_memory(redis_malloc_size(ptr));
update_zmalloc_stat_alloc(redis_malloc_size(ptr),size);
return ptr;
#else
*((size_t*)ptr) = size;
increment_used_memory(size+PREFIX_SIZE);
update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);
return (char*)ptr+PREFIX_SIZE;
#endif
}
@@ -128,8 +129,8 @@ void *zrealloc(void *ptr, size_t size) {
newptr = realloc(ptr,size);
if (!newptr) zmalloc_oom(size);
decrement_used_memory(oldsize);
increment_used_memory(redis_malloc_size(newptr));
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(redis_malloc_size(newptr),size);
return newptr;
#else
realptr = (char*)ptr-PREFIX_SIZE;
@@ -138,8 +139,8 @@ void *zrealloc(void *ptr, size_t size) {
if (!newptr) zmalloc_oom(size);
*((size_t*)newptr) = size;
decrement_used_memory(oldsize);
increment_used_memory(size);
update_zmalloc_stat_free(oldsize);
update_zmalloc_stat_alloc(size,size);
return (char*)newptr+PREFIX_SIZE;
#endif
}
@@ -152,12 +153,12 @@ void zfree(void *ptr) {
if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
decrement_used_memory(redis_malloc_size(ptr));
update_zmalloc_stat_free(redis_malloc_size(ptr));
free(ptr);
#else
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
decrement_used_memory(oldsize+PREFIX_SIZE);
update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
free(realptr);
#endif
}
@@ -251,7 +252,7 @@ size_t zmalloc_get_rss(void) {
return t_info.resident_size;
}
#else
float zmalloc_get_rss(void) {
size_t zmalloc_get_rss(void) {
/* If we can't get the RSS in an OS-specific way for this system just
* return the memory usage we estimated in zmalloc()..
*
+65 -25
View File
@@ -31,13 +31,14 @@ tags {"aof"} {
}
start_server_aof [list dir $server_path] {
test {Unfinished MULTI: Server should not have been started} {
is_alive $srv
} {0}
test "Unfinished MULTI: Server should not have been started" {
assert_equal 0 [is_alive $srv]
}
test {Unfinished MULTI: Server should have logged an error} {
exec cat [dict get $srv stdout] | tail -n1
} {*Unexpected end of file reading the append only file*}
test "Unfinished MULTI: Server should have logged an error" {
set result [exec cat [dict get $srv stdout] | tail -n1]
assert_match "*Unexpected end of file reading the append only file*" $result
}
}
## Test that the server exits when the AOF contains a short read
@@ -47,36 +48,75 @@ tags {"aof"} {
}
start_server_aof [list dir $server_path] {
test {Short read: Server should not have been started} {
is_alive $srv
} {0}
test "Short read: Server should not have been started" {
assert_equal 0 [is_alive $srv]
}
test {Short read: Server should have logged an error} {
exec cat [dict get $srv stdout] | tail -n1
} {*Bad file format reading the append only file*}
test "Short read: Server should have logged an error" {
set result [exec cat [dict get $srv stdout] | tail -n1]
assert_match "*Bad file format reading the append only file*" $result
}
}
## Test that redis-check-aof indeed sees this AOF is not valid
test {Short read: Utility should confirm the AOF is not valid} {
test "Short read: Utility should confirm the AOF is not valid" {
catch {
exec src/redis-check-aof $aof_path
} str
set _ $str
} {*not valid*}
} result
assert_match "*not valid*" $result
}
test {Short read: Utility should be able to fix the AOF} {
exec echo y | src/redis-check-aof --fix $aof_path
} {*Successfully truncated AOF*}
test "Short read: Utility should be able to fix the AOF" {
set result [exec echo y | src/redis-check-aof --fix $aof_path]
assert_match "*Successfully truncated AOF*" $result
}
## Test that the server can be started using the truncated AOF
start_server_aof [list dir $server_path] {
test {Fixed AOF: Server should have been started} {
is_alive $srv
} {1}
test "Fixed AOF: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
test {Fixed AOF: Keyspace should contain values that were parsable} {
test "Fixed AOF: Keyspace should contain values that were parsable" {
set client [redis [dict get $srv host] [dict get $srv port]]
list [$client get foo] [$client get bar]
} {hello {}}
assert_equal "hello" [$client get foo]
assert_equal "" [$client get bar]
}
}
## Test that SPOP (that modifies the client its argc/argv) is correctly free'd
create_aof {
append_to_aof [formatCommand sadd set foo]
append_to_aof [formatCommand sadd set bar]
append_to_aof [formatCommand spop set]
}
start_server_aof [list dir $server_path] {
test "AOF+SPOP: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]]
assert_equal 1 [$client scard set]
}
}
## Test that EXPIREAT is loaded correctly
create_aof {
append_to_aof [formatCommand rpush list foo]
append_to_aof [formatCommand expireat list 1000]
append_to_aof [formatCommand rpush list bar]
}
start_server_aof [list dir $server_path] {
test "AOF+EXPIRE: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
test "AOF+EXPIRE: List should be empty" {
set client [redis [dict get $srv host] [dict get $srv port]]
assert_equal 0 [$client llen list]
}
}
}
+20
View File
@@ -6,6 +6,24 @@ start_server {tags {"repl"}} {
s -1 role
} {slave}
test {BRPOPLPUSH replication, when blocking against empty list} {
set rd [redis_deferring_client]
$rd brpoplpush a b 5
r lpush a foo
after 1000
assert_equal [r debug digest] [r -1 debug digest]
}
test {BRPOPLPUSH replication, list exists} {
set rd [redis_deferring_client]
r lpush c 1
r lpush c 2
r lpush c 3
$rd brpoplpush c d 5
after 1000
assert_equal [r debug digest] [r -1 debug digest]
}
test {MASTER and SLAVE dataset should be identical after complex ops} {
createComplexDataset r 10000
after 500
@@ -27,6 +45,8 @@ start_server {tags {"repl"}} {
test {MASTER and SLAVE consistency with expire} {
createComplexDataset r 50000 useexpire
after 4000 ;# Make sure everything expired before taking the digest
r keys * ;# Force DEL syntesizing to slave
after 1000 ;# Wait another second. Now everything should be fine.
if {[r debug digest] ne [r -1 debug digest]} {
set csv1 [csvdump r]
set csv2 [csvdump {r -1}]
+36 -33
View File
@@ -83,9 +83,13 @@ proc ping_server {host port} {
}
close $fd
} e]} {
puts -nonewline "."
if {$::verbose} {
puts -nonewline "."
}
} else {
puts -nonewline "ok"
if {$::verbose} {
puts -nonewline "ok"
}
}
return $retval
}
@@ -171,7 +175,7 @@ proc start_server {options {code undefined}} {
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
if {$::valgrind} {
exec valgrind src/redis-server $config_file > $stdout 2> $stderr &
exec valgrind --suppressions=src/valgrind.sup src/redis-server $config_file > $stdout 2> $stderr &
} else {
exec src/redis-server $config_file > $stdout 2> $stderr &
}
@@ -181,7 +185,10 @@ proc start_server {options {code undefined}} {
set retrynum 20
set serverisup 0
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
if {$::verbose} {
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
}
after 10
if {$code ne "undefined"} {
while {[incr retrynum -1]} {
@@ -196,7 +203,10 @@ proc start_server {options {code undefined}} {
} else {
set serverisup 1
}
puts {}
if {$::verbose} {
puts ""
}
if {!$serverisup} {
error_and_quit $config_file [exec cat $stderr]
@@ -246,41 +256,34 @@ proc start_server {options {code undefined}} {
reconnect
# execute provided block
set curnum $::testnum
if {![catch { uplevel 1 $code } err]} {
# zero exit status is good
unset err
set num_tests $::num_tests
if {[catch { uplevel 1 $code } error]} {
set backtrace $::errorInfo
# Kill the server without checking for leaks
dict set srv "skipleaks" 1
kill_server $srv
# Print warnings from log
puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]]
set warnings [warnings_from_file [dict get $srv "stdout"]]
if {[string length $warnings] > 0} {
puts "$warnings"
} else {
puts "(none)"
}
puts ""
error $error $backtrace
}
if {$curnum == $::testnum} {
# don't check for leaks when no tests were executed
# Don't do the leak check when no tests were run
if {$num_tests == $::num_tests} {
dict set srv "skipleaks" 1
}
# pop the server object
set ::servers [lrange $::servers 0 end-1]
# allow an exception to bubble up the call chain but still kill this
# server, because we want to reuse the ports when the tests are re-run
if {[info exists err]} {
if {$err eq "exception"} {
puts [format "Logged warnings (pid %d):" [dict get $srv "pid"]]
set warnings [warnings_from_file [dict get $srv "stdout"]]
if {[string length $warnings] > 0} {
puts "$warnings"
} else {
puts "(none)"
}
# kill this server without checking for leaks
dict set srv "skipleaks" 1
kill_server $srv
error "exception"
} elseif {[string length $err] > 0} {
puts "Error executing the suite, aborting..."
puts $err
exit 1
}
}
set ::tags [lrange $::tags 0 end-[llength $tags]]
kill_server $srv
+116 -26
View File
@@ -1,25 +1,23 @@
set ::passed 0
set ::failed 0
set ::testnum 0
set ::num_tests 0
set ::num_passed 0
set ::num_failed 0
set ::tests_failed {}
proc assert {condition} {
if {![uplevel 1 expr $condition]} {
puts "!! ERROR\nExpected '$value' to evaluate to true"
error "assertion"
error "assertion:Expected '$value' to be true"
}
}
proc assert_match {pattern value} {
if {![string match $pattern $value]} {
puts "!! ERROR\nExpected '$value' to match '$pattern'"
error "assertion"
error "assertion:Expected '$value' to match '$pattern'"
}
}
proc assert_equal {expected value} {
if {$expected ne $value} {
puts "!! ERROR\nExpected '$value' to be equal to '$expected'"
error "assertion"
error "assertion:Expected '$value' to be equal to '$expected'"
}
}
@@ -27,8 +25,7 @@ proc assert_error {pattern code} {
if {[catch {uplevel 1 $code} error]} {
assert_match $pattern $error
} else {
puts "!! ERROR\nExpected an error but nothing was catched"
error "assertion"
error "assertion:Expected an error but nothing was catched"
}
}
@@ -47,7 +44,66 @@ proc assert_type {type key} {
assert_equal $type [r type $key]
}
proc test {name code {okpattern notspecified}} {
# Test if TERM looks like to support colors
proc color_term {} {
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
}
# This is called before starting the test
proc announce_test {s} {
if {[color_term]} {
puts -nonewline "$s\033\[0K"
flush stdout
set ::backward_count [string length $s]
}
}
# This is called after the test finished
proc colored_dot {tags passed} {
if {[color_term]} {
# Go backward and delete what announc_test function printed.
puts -nonewline "\033\[${::backward_count}D\033\[0K\033\[J"
# Print a coloured char, accordingly to test outcome and tags.
if {[lsearch $tags list] != -1} {
set colorcode {31}
set ch L
} elseif {[lsearch $tags hash] != -1} {
set colorcode {32}
set ch H
} elseif {[lsearch $tags set] != -1} {
set colorcode {33}
set ch S
} elseif {[lsearch $tags zset] != -1} {
set colorcode {34}
set ch Z
} elseif {[lsearch $tags basic] != -1} {
set colorcode {35}
set ch B
} else {
set colorcode {37}
set ch .
}
if {$colorcode ne {}} {
if {$passed} {
puts -nonewline "\033\[0;${colorcode};40m"
} else {
puts -nonewline "\033\[7;${colorcode};40m"
}
puts -nonewline $ch
puts -nonewline "\033\[0m"
flush stdout
}
} else {
if {$passed} {
puts -nonewline .
} else {
puts -nonewline F
}
}
}
proc test {name code {okpattern undefined}} {
# abort if tagged with a tag to deny
foreach tag $::denytags {
if {[lsearch $::tags $tag] >= 0} {
@@ -69,30 +125,64 @@ proc test {name code {okpattern notspecified}} {
}
}
incr ::testnum
puts -nonewline [format "#%03d %-68s " $::testnum $name]
flush stdout
incr ::num_tests
set details {}
lappend details $::curfile
lappend details $::tags
lappend details $name
if {$::verbose} {
puts -nonewline [format "#%03d %-68s " $::num_tests $name]
flush stdout
} else {
announce_test $name
}
if {[catch {set retval [uplevel 1 $code]} error]} {
if {$error eq "assertion"} {
incr ::failed
if {[string match "assertion:*" $error]} {
set msg [string range $error 10 end]
lappend details $msg
lappend ::tests_failed $details
incr ::num_failed
if {$::verbose} {
puts "FAILED"
puts "$msg\n"
} else {
colored_dot $::tags 0
}
} else {
puts "EXCEPTION"
puts "\nCaught error: $error"
error "exception"
# Re-raise, let handler up the stack take care of this.
error $error $::errorInfo
}
} else {
if {$okpattern eq "notspecified" || $okpattern eq $retval || [string match $okpattern $retval]} {
puts "PASSED"
incr ::passed
if {$okpattern eq "undefined" || $okpattern eq $retval || [string match $okpattern $retval]} {
incr ::num_passed
if {$::verbose} {
puts "PASSED"
} else {
colored_dot $::tags 1
}
} else {
puts "!! ERROR expected\n'$okpattern'\nbut got\n'$retval'"
incr ::failed
set msg "Expected '$okpattern' to equal or match '$retval'"
lappend details $msg
lappend ::tests_failed $details
incr ::num_failed
if {$::verbose} {
puts "FAILED"
puts "$msg\n"
} else {
colored_dot $::tags 0
}
}
}
flush stdout
if {$::traceleaks} {
set output [exec leaks redis-server]
if {![string match {*0 leaks*} $output]} {
puts "--------- Test $::testnum LEAKED! --------"
puts "--- Test \"$name\" leaked! ---"
puts $output
exit 1
}
+8 -4
View File
@@ -52,8 +52,10 @@ proc status {r property} {
proc waitForBgsave r {
while 1 {
if {[status r bgsave_in_progress] eq 1} {
puts -nonewline "\nWaiting for background save to finish... "
flush stdout
if {$::verbose} {
puts -nonewline "\nWaiting for background save to finish... "
flush stdout
}
after 1000
} else {
break
@@ -64,8 +66,10 @@ proc waitForBgsave r {
proc waitForBgrewriteaof r {
while 1 {
if {[status r bgrewriteaof_in_progress] eq 1} {
puts -nonewline "\nWaiting for background AOF rewrite to finish... "
flush stdout
if {$::verbose} {
puts -nonewline "\nWaiting for background AOF rewrite to finish... "
flush stdout
}
after 1000
} else {
break
+34 -6
View File
@@ -13,13 +13,17 @@ set ::host 127.0.0.1
set ::port 16379
set ::traceleaks 0
set ::valgrind 0
set ::verbose 0
set ::denytags {}
set ::allowtags {}
set ::external 0; # If "1" this means, we are running against external instance
set ::file ""; # If set, runs only the tests in this comma separated list
set ::curfile ""; # Hold the filename of the current suite
proc execute_tests name {
source "tests/$name.tcl"
set path "tests/$name.tcl"
set ::curfile $path
source $path
}
# Setup a list to hold a stack of server configs. When calls to start_server
@@ -104,6 +108,7 @@ proc cleanup {} {
}
proc execute_everything {} {
execute_tests "unit/printver"
execute_tests "unit/auth"
execute_tests "unit/protocol"
execute_tests "unit/basic"
@@ -120,9 +125,10 @@ proc execute_everything {} {
execute_tests "integration/aof"
# execute_tests "integration/redis-cli"
execute_tests "unit/pubsub"
execute_tests "unit/slowlog"
# run tests with VM enabled
set ::global_overrides {vm-enabled yes}
set ::global_overrides {vm-enabled yes really-use-vm yes}
execute_tests "unit/protocol"
execute_tests "unit/basic"
execute_tests "unit/type/list"
@@ -147,9 +153,27 @@ proc main {} {
}
cleanup
puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed"
if {$::failed > 0} {
puts "\n*** WARNING!!! $::failed FAILED TESTS ***\n"
puts "\n[expr $::num_tests] tests, $::num_passed passed, $::num_failed failed\n"
if {$::num_failed > 0} {
set curheader ""
puts "Failures:"
foreach {test} $::tests_failed {
set header [lindex $test 0]
append header " ("
append header [join [lindex $test 1] ","]
append header ")"
if {$curheader ne $header} {
set curheader $header
puts "\n$curheader:"
}
set name [lindex $test 2]
set msg [lindex $test 3]
puts "- $name: $msg"
}
puts ""
exit 1
}
}
@@ -167,6 +191,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
}
}
incr j
} elseif {$opt eq {--valgrind}} {
set ::valgrind 1
} elseif {$opt eq {--file}} {
set ::file $arg
incr j
@@ -177,6 +203,8 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--port}} {
set ::port $arg
incr j
} elseif {$opt eq {--verbose}} {
set ::verbose 1
} else {
puts "Wrong argument: $opt"
exit 1
@@ -187,7 +215,7 @@ if {[catch { main } err]} {
if {[string length $err] > 0} {
# only display error when not generated by the test suite
if {$err ne "exception"} {
puts $err
puts $::errorInfo
}
exit 1
}
+258 -19
View File
@@ -138,21 +138,48 @@ start_server {tags {"basic"}} {
r decrby novar 17179869185
} {-1}
test {SETNX target key missing} {
r setnx novar2 foobared
r get novar2
} {foobared}
test "SETNX target key missing" {
r del novar
assert_equal 1 [r setnx novar foobared]
assert_equal "foobared" [r get novar]
}
test {SETNX target key exists} {
r setnx novar2 blabla
r get novar2
} {foobared}
test "SETNX target key exists" {
r set novar foobared
assert_equal 0 [r setnx novar blabla]
assert_equal "foobared" [r get novar]
}
test {SETNX against volatile key} {
test "SETNX against not-expired volatile key" {
r set x 10
r expire x 10000
list [r setnx x 20] [r get x]
} {0 10}
assert_equal 0 [r setnx x 20]
assert_equal 10 [r get x]
}
test "SETNX against expired volatile key" {
# Make it very unlikely for the key this test uses to be expired by the
# active expiry cycle. This is tightly coupled to the implementation of
# active expiry and dbAdd() but currently the only way to test that
# SETNX expires a key when it should have been.
for {set x 0} {$x < 9999} {incr x} {
r setex key-$x 3600 value
}
# This will be one of 10000 expiring keys. A cycle is executed every
# 100ms, sampling 10 keys for being expired or not. This key will be
# expired for at most 1s when we wait 2s, resulting in a total sample
# of 100 keys. The probability of the success of this test being a
# false positive is therefore approx. 1%.
r set x 10
r expire x 1
# Wait for the key to expire
after 2000
assert_equal 1 [r setnx x 20]
assert_equal 20 [r get x]
}
test {EXISTS} {
set res {}
@@ -361,17 +388,229 @@ start_server {tags {"basic"}} {
list [r msetnx x1 xxx y2 yyy] [r get x1] [r get y2]
} {1 xxx yyy}
test {STRLEN against non existing key} {
r strlen notakey
} {0}
test "STRLEN against non-existing key" {
assert_equal 0 [r strlen notakey]
}
test {STRLEN against integer} {
test "STRLEN against integer-encoded value" {
r set myinteger -555
r strlen myinteger
} {4}
assert_equal 4 [r strlen myinteger]
}
test {STRLEN against plain string} {
test "STRLEN against plain string" {
r set mystring "foozzz0123456789 baz"
r strlen mystring
assert_equal 20 [r strlen mystring]
}
test "SETBIT against non-existing key" {
r del mykey
assert_equal 0 [r setbit mykey 1 1]
assert_equal [binary format B* 01000000] [r get mykey]
}
test "SETBIT against string-encoded key" {
# Ascii "@" is integer 64 = 01 00 00 00
r set mykey "@"
assert_equal 0 [r setbit mykey 2 1]
assert_equal [binary format B* 01100000] [r get mykey]
assert_equal 1 [r setbit mykey 1 0]
assert_equal [binary format B* 00100000] [r get mykey]
}
test "SETBIT against integer-encoded key" {
# Ascii "1" is integer 49 = 00 11 00 01
r set mykey 1
assert_encoding int mykey
assert_equal 0 [r setbit mykey 6 1]
assert_equal [binary format B* 00110011] [r get mykey]
assert_equal 1 [r setbit mykey 2 0]
assert_equal [binary format B* 00010011] [r get mykey]
}
test "SETBIT against key with wrong type" {
r del mykey
r lpush mykey "foo"
assert_error "*wrong kind*" {r setbit mykey 0 1}
}
test "SETBIT with out of range bit offset" {
r del mykey
assert_error "*out of range*" {r setbit mykey [expr 4*1024*1024*1024] 1}
assert_error "*out of range*" {r setbit mykey -1 1}
}
test "SETBIT with non-bit argument" {
r del mykey
assert_error "*out of range*" {r setbit mykey 0 -1}
assert_error "*out of range*" {r setbit mykey 0 2}
assert_error "*out of range*" {r setbit mykey 0 10}
assert_error "*out of range*" {r setbit mykey 0 20}
}
test "SETBIT fuzzing" {
set str ""
set len [expr 256*8]
r del mykey
for {set i 0} {$i < 2000} {incr i} {
set bitnum [randomInt $len]
set bitval [randomInt 2]
set fmt [format "%%-%ds%%d%%-s" $bitnum]
set head [string range $str 0 $bitnum-1]
set tail [string range $str $bitnum+1 end]
set str [string map {" " 0} [format $fmt $head $bitval $tail]]
r setbit mykey $bitnum $bitval
assert_equal [binary format B* $str] [r get mykey]
}
}
test "GETBIT against non-existing key" {
r del mykey
assert_equal 0 [r getbit mykey 0]
}
test "GETBIT against string-encoded key" {
# Single byte with 2nd and 3rd bit set
r set mykey "`"
# In-range
assert_equal 0 [r getbit mykey 0]
assert_equal 1 [r getbit mykey 1]
assert_equal 1 [r getbit mykey 2]
assert_equal 0 [r getbit mykey 3]
# Out-range
assert_equal 0 [r getbit mykey 8]
assert_equal 0 [r getbit mykey 100]
assert_equal 0 [r getbit mykey 10000]
}
test "GETBIT against integer-encoded key" {
r set mykey 1
assert_encoding int mykey
# Ascii "1" is integer 49 = 00 11 00 01
assert_equal 0 [r getbit mykey 0]
assert_equal 0 [r getbit mykey 1]
assert_equal 1 [r getbit mykey 2]
assert_equal 1 [r getbit mykey 3]
# Out-range
assert_equal 0 [r getbit mykey 8]
assert_equal 0 [r getbit mykey 100]
assert_equal 0 [r getbit mykey 10000]
}
test "SETRANGE against non-existing key" {
r del mykey
assert_equal 3 [r setrange mykey 0 foo]
assert_equal "foo" [r get mykey]
r del mykey
assert_equal 0 [r setrange mykey 0 ""]
assert_equal 0 [r exists mykey]
r del mykey
assert_equal 4 [r setrange mykey 1 foo]
assert_equal "\000foo" [r get mykey]
}
test "SETRANGE against string-encoded key" {
r set mykey "foo"
assert_equal 3 [r setrange mykey 0 b]
assert_equal "boo" [r get mykey]
r set mykey "foo"
assert_equal 3 [r setrange mykey 0 ""]
assert_equal "foo" [r get mykey]
r set mykey "foo"
assert_equal 3 [r setrange mykey 1 b]
assert_equal "fbo" [r get mykey]
r set mykey "foo"
assert_equal 7 [r setrange mykey 4 bar]
assert_equal "foo\000bar" [r get mykey]
}
test "SETRANGE against integer-encoded key" {
r set mykey 1234
assert_encoding int mykey
assert_equal 4 [r setrange mykey 0 2]
assert_encoding raw mykey
assert_equal 2234 [r get mykey]
# Shouldn't change encoding when nothing is set
r set mykey 1234
assert_encoding int mykey
assert_equal 4 [r setrange mykey 0 ""]
assert_encoding int mykey
assert_equal 1234 [r get mykey]
r set mykey 1234
assert_encoding int mykey
assert_equal 4 [r setrange mykey 1 3]
assert_encoding raw mykey
assert_equal 1334 [r get mykey]
r set mykey 1234
assert_encoding int mykey
assert_equal 6 [r setrange mykey 5 2]
assert_encoding raw mykey
assert_equal "1234\0002" [r get mykey]
}
test "SETRANGE against key with wrong type" {
r del mykey
r lpush mykey "foo"
assert_error "*wrong kind*" {r setrange mykey 0 bar}
}
test "SETRANGE with out of range offset" {
r del mykey
assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world}
r set mykey "hello"
assert_error "*out of range*" {r setrange mykey -1 world}
assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world}
}
test "GETRANGE against non-existing key" {
r del mykey
assert_equal "" [r getrange mykey 0 -1]
}
test "GETRANGE against string value" {
r set mykey "Hello World"
assert_equal "Hell" [r getrange mykey 0 3]
assert_equal "Hello World" [r getrange mykey 0 -1]
assert_equal "orld" [r getrange mykey -4 -1]
assert_equal "" [r getrange mykey 5 3]
assert_equal " World" [r getrange mykey 5 5000]
assert_equal "Hello World" [r getrange mykey -5000 10000]
}
test "GETRANGE against integer-encoded value" {
r set mykey 1234
assert_equal "123" [r getrange mykey 0 2]
assert_equal "1234" [r getrange mykey 0 -1]
assert_equal "234" [r getrange mykey -3 -1]
assert_equal "" [r getrange mykey 5 3]
assert_equal "4" [r getrange mykey 3 5000]
assert_equal "1234" [r getrange mykey -5000 10000]
}
test "GETRANGE fuzzing" {
for {set i 0} {$i < 1000} {incr i} {
r set bin [set bin [randstring 0 1024 binary]]
set _start [set start [randomInt 1500]]
set _end [set end [randomInt 1500]]
if {$_start < 0} {set _start "end-[abs($_start)-1]"}
if {$_end < 0} {set _end "end-[abs($_end)-1]"}
assert_equal [string range $bin $_start $_end] [r getrange bin $start $end]
}
}
}
+7 -36
View File
@@ -174,6 +174,13 @@ start_server {tags {"other"}} {
set _ $err
} {*ERR MULTI*}
test {MULTI where commands alter argc/argv} {
r sadd myset a
r multi
r spop myset
list [r exec] [r exists myset]
} {a 0}
test {WATCH inside MULTI is not allowed} {
set err {}
r multi
@@ -216,42 +223,6 @@ start_server {tags {"other"}} {
set _ $err
} {}
test {SUBSTR basics} {
set res {}
r set foo "Hello World"
lappend res [r substr foo 0 3]
lappend res [r substr foo 0 -1]
lappend res [r substr foo -4 -1]
lappend res [r substr foo 5 3]
lappend res [r substr foo 5 5000]
lappend res [r substr foo -5000 10000]
set _ $res
} {Hell {Hello World} orld {} { World} {Hello World}}
test {SUBSTR against integer encoded values} {
r set foo 123
r substr foo 0 -2
} {12}
test {SUBSTR fuzzing} {
set err {}
for {set i 0} {$i < 1000} {incr i} {
set bin [randstring 0 1024 binary]
set _start [set start [randomInt 1500]]
set _end [set end [randomInt 1500]]
if {$_start < 0} {set _start "end-[abs($_start)-1]"}
if {$_end < 0} {set _end "end-[abs($_end)-1]"}
set s1 [string range $bin $_start $_end]
r set bin $bin
set s2 [r substr bin $start $end]
if {$s1 != $s2} {
set err "String mismatch"
break
}
}
set _ $err
} {}
# Leave the user with a clean DB before to exit
test {FLUSHDB} {
set aux {}

Some files were not shown because too many files have changed in this diff Show More