Compare commits
170
Commits
argv-accounting
...
2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ff874d2b2 | ||
|
|
f97b01d36a | ||
|
|
991eee4d01 | ||
|
|
74b77ff8a6 | ||
|
|
15da88c8dc | ||
|
|
ee2f20dfde | ||
|
|
0dfc5578bf | ||
|
|
ee9f3679a9 | ||
|
|
28de624c70 | ||
|
|
5d4675979d | ||
|
|
9e087a298d | ||
|
|
8ac3c86664 | ||
|
|
a8ed663376 | ||
|
|
61f57b6a8f | ||
|
|
24a1580df5 | ||
|
|
4793a2c3c3 | ||
|
|
1d05b53fcc | ||
|
|
e1f01c9b28 | ||
|
|
c1b270127c | ||
|
|
62e6f6c29a | ||
|
|
5960ac9dec | ||
|
|
ec7bbadce9 | ||
|
|
063923de1c | ||
|
|
aa96ebec89 | ||
|
|
563f4cfee5 | ||
|
|
e8108591e4 | ||
|
|
eed4ec4664 | ||
|
|
9702c92c4f | ||
|
|
3fee7e3013 | ||
|
|
891f9196fc | ||
|
|
59aecb3a6c | ||
|
|
b8082ae7ba | ||
|
|
d30dafe7f4 | ||
|
|
a906670e2d | ||
|
|
d3203c16f5 | ||
|
|
2e1adff83e | ||
|
|
194058931f | ||
|
|
5a7bfb8a84 | ||
|
|
aaa7fe1551 | ||
|
|
33d2761bc0 | ||
|
|
ad6347b74e | ||
|
|
d3b5989148 | ||
|
|
a2d68e6524 | ||
|
|
25e805b417 | ||
|
|
4ec8b732b7 | ||
|
|
046b4f66ef | ||
|
|
5d91926304 | ||
|
|
5afce9b6ea | ||
|
|
0df2e5b9d5 | ||
|
|
0b4eb5fd21 | ||
|
|
b13216a8c7 | ||
|
|
25a585ad42 | ||
|
|
7d8c021a9a | ||
|
|
be87e76906 | ||
|
|
09d9879963 | ||
|
|
dfc74051ce | ||
|
|
196fc32b77 | ||
|
|
b190b0c98f | ||
|
|
d7061f8137 | ||
|
|
20867e8009 | ||
|
|
10e987c5fd | ||
|
|
cc958ccf42 | ||
|
|
ec279203df | ||
|
|
480a2e73cf | ||
|
|
f447a7ebb4 | ||
|
|
748a2da3e8 | ||
|
|
b02e81be07 | ||
|
|
430719ca53 | ||
|
|
cc8a0f898b | ||
|
|
cd128d2882 | ||
|
|
c717adbc2e | ||
|
|
1ffa5d73ed | ||
|
|
a7fa2baf06 | ||
|
|
f7aef5241b | ||
|
|
c86a4f9102 | ||
|
|
2e638590ad | ||
|
|
3554f09ddc | ||
|
|
ccab83e729 | ||
|
|
97ddfbbfc3 | ||
|
|
50e50d6a25 | ||
|
|
9d665825d9 | ||
|
|
19b55f8ebc | ||
|
|
bf3692797d | ||
|
|
da06854477 | ||
|
|
38de9362b2 | ||
|
|
7600994937 | ||
|
|
6a9764d183 | ||
|
|
c138dc7da4 | ||
|
|
1b905277bb | ||
|
|
2b886275e9 | ||
|
|
93db956e06 | ||
|
|
27fc6199ca | ||
|
|
0b01578d2e | ||
|
|
920c45b818 | ||
|
|
cf6c3f4b04 | ||
|
|
fb90934c47 | ||
|
|
207ca3cedd | ||
|
|
97d3b7dc8d | ||
|
|
abdbfc14c0 | ||
|
|
de5d4be994 | ||
|
|
69bfdcf7d4 | ||
|
|
699465c31e | ||
|
|
45332cc47b | ||
|
|
2d2c17b075 | ||
|
|
048c0f0f37 | ||
|
|
3eb3cc9da4 | ||
|
|
74f6ee8dd5 | ||
|
|
7a55d72f28 | ||
|
|
4e4b3b943c | ||
|
|
61e2547675 | ||
|
|
595b5974f8 | ||
|
|
58418d7c3e | ||
|
|
c02bda324f | ||
|
|
1afef16d88 | ||
|
|
7377aacd84 | ||
|
|
eece2d528c | ||
|
|
eb5aeaf138 | ||
|
|
1a6281f52f | ||
|
|
c2571b314a | ||
|
|
97aeda9828 | ||
|
|
ca9d961073 | ||
|
|
2446fbb3bb | ||
|
|
970e813be2 | ||
|
|
a36d524562 | ||
|
|
bbaf76ea2f | ||
|
|
669f302e16 | ||
|
|
71791e7a8e | ||
|
|
2f91d7ada4 | ||
|
|
5ef8f9531d | ||
|
|
3ce30a6906 | ||
|
|
65e79a8272 | ||
|
|
ef8bc13d0d | ||
|
|
0ffeadd290 | ||
|
|
db0e263b42 | ||
|
|
d526d09e41 | ||
|
|
9738e587da | ||
|
|
da67b5f59a | ||
|
|
825d2904d1 | ||
|
|
3becef9ee6 | ||
|
|
2ccf6e81a6 | ||
|
|
e18b59ae7e | ||
|
|
8ff1353766 | ||
|
|
99677e89d0 | ||
|
|
f43502fb0e | ||
|
|
ebb07fb4bb | ||
|
|
c13c4080e5 | ||
|
|
360664c9c8 | ||
|
|
7ac1b364fe | ||
|
|
1b2b8cbbde | ||
|
|
f487bbbfe2 | ||
|
|
4b2499cf2f | ||
|
|
01a0b09886 | ||
|
|
90419b5681 | ||
|
|
ae7db3fe83 | ||
|
|
454f4bfd28 | ||
|
|
1408d147e4 | ||
|
|
78055bd4ca | ||
|
|
736d447380 | ||
|
|
71b6f64f2a | ||
|
|
c4e93cadfb | ||
|
|
0540df22ba | ||
|
|
9ac5be2ebe | ||
|
|
b902c1413e | ||
|
|
54e9f28922 | ||
|
|
dd889d884f | ||
|
|
1443a814d0 | ||
|
|
076ff11c06 | ||
|
|
6418b4c790 | ||
|
|
4769dc7826 | ||
|
|
039357e471 |
+203
-3
@@ -1,4 +1,202 @@
|
||||
Welcome to Redis 2.1.9 (2.2 Release Candidate 1)
|
||||
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
|
||||
@@ -7,7 +205,8 @@ 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.x
|
||||
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.
|
||||
@@ -26,10 +225,11 @@ WHAT'S NEW IN REDIS 2.2.x
|
||||
* 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 process information in the INFO output.
|
||||
* 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.
|
||||
|
||||
@@ -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!
|
||||
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
Vendored
+4
-1
@@ -15,7 +15,8 @@ ifeq ($(uname_S),SunOS)
|
||||
DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?=-lm -pthread
|
||||
LDFLAGS?=-L. -Wl,-rpath,.
|
||||
@@ -33,6 +34,8 @@ else
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
endif
|
||||
endif
|
||||
|
||||
CCOPT= $(CFLAGS) $(CCLINK)
|
||||
DEBUG?= -g -ggdb
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -50,7 +50,7 @@ typedef struct redisReader {
|
||||
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;
|
||||
@@ -340,9 +340,9 @@ static int processMultiBulkItem(redisReader *r) {
|
||||
int root = 0;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 1 */
|
||||
if (r->ridx == 2) {
|
||||
if (r->ridx == 8) {
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"No support for nested multi bulk replies with depth > 1"));
|
||||
"No support for nested multi bulk replies with depth > 7"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+13
-2
@@ -320,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;
|
||||
@@ -337,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];
|
||||
|
||||
+30
-7
@@ -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
|
||||
@@ -291,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
|
||||
@@ -365,17 +393,12 @@ 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
|
||||
|
||||
+41
-20
@@ -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
|
||||
@@ -50,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 \
|
||||
@@ -72,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 \
|
||||
@@ -103,42 +121,45 @@ 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)
|
||||
$(QUIET_LINK)$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ) $(CCLINK)
|
||||
|
||||
redis-benchmark: dependencies $(BENCHOBJ)
|
||||
cd ../deps/hiredis && $(MAKE) static
|
||||
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a
|
||||
@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: dependencies $(CLIOBJ)
|
||||
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o
|
||||
$(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
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
$(CC) -MM *.c -I ../deps/hiredis -I ../deps/linenoise
|
||||
|
||||
test: redis-server
|
||||
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
|
||||
|
||||
+15
-15
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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? */
|
||||
|
||||
+95
-5
@@ -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) {
|
||||
@@ -194,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;
|
||||
@@ -245,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]);
|
||||
@@ -290,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;
|
||||
}
|
||||
@@ -299,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:
|
||||
@@ -307,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);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -428,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);
|
||||
@@ -450,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);
|
||||
@@ -543,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+12
-1
@@ -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));
|
||||
@@ -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
@@ -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
@@ -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
@@ -3,7 +3,7 @@
|
||||
|
||||
#define _BSD_SOURCE
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) || defined(__OpenBSD__)
|
||||
#define _XOPEN_SOURCE 700
|
||||
#else
|
||||
#define _XOPEN_SOURCE
|
||||
|
||||
+42
-12
@@ -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" }
|
||||
|
||||
+12
-3
@@ -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);
|
||||
|
||||
+88
-98
@@ -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;
|
||||
@@ -60,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 ||
|
||||
@@ -88,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;
|
||||
@@ -105,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);
|
||||
@@ -128,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 {
|
||||
@@ -148,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 {
|
||||
@@ -165,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);
|
||||
@@ -396,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);
|
||||
@@ -411,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);
|
||||
@@ -424,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) {
|
||||
@@ -457,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) {
|
||||
@@ -491,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 */
|
||||
@@ -516,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) {
|
||||
@@ -586,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);
|
||||
|
||||
@@ -595,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);
|
||||
@@ -908,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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -143,7 +143,7 @@ static void randomizeClientKey(client c) {
|
||||
|
||||
for (i = 0; i < c->randlen; i++) {
|
||||
r = random() % config.randomkeys_keyspacelen;
|
||||
snprintf(buf,sizeof(buf),"%012lu",r);
|
||||
snprintf(buf,sizeof(buf),"%012zu",r);
|
||||
memcpy(c->randptr[i],buf,12);
|
||||
}
|
||||
}
|
||||
|
||||
+140
-38
@@ -55,16 +55,18 @@ static struct config {
|
||||
int hostport;
|
||||
char *hostsocket;
|
||||
long repeat;
|
||||
long interval;
|
||||
int dbnum;
|
||||
int interactive;
|
||||
int shutdown;
|
||||
int monitor_mode;
|
||||
int pubsub_mode;
|
||||
int latency_mode;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char *auth;
|
||||
char *historyfile;
|
||||
int raw_output; /* output mode per command */
|
||||
sds mb_delim;
|
||||
char prompt[32];
|
||||
} config;
|
||||
|
||||
static void usage();
|
||||
@@ -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
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -264,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;
|
||||
@@ -309,10 +318,9 @@ 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 cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
@@ -389,15 +397,18 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
switch (r->type) {
|
||||
case REDIS_REPLY_NIL:
|
||||
/* Nothing... */
|
||||
break;
|
||||
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;
|
||||
break;
|
||||
case REDIS_REPLY_INTEGER:
|
||||
out = sdscatprintf(out,"%lld",r->integer);
|
||||
break;
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
if (i > 0) out = sdscat(out,config.mb_delim);
|
||||
@@ -405,7 +416,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||
sdsfree(tmp);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
||||
exit(1);
|
||||
@@ -414,10 +425,11 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -427,10 +439,12 @@ static int cliReadReply(int output_raw_strings) {
|
||||
if (context->err == REDIS_ERR_EOF)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
cliPrintContextErrorAndExit();
|
||||
cliPrintContextError();
|
||||
exit(1);
|
||||
return REDIS_ERR; /* avoid compiler warning */
|
||||
}
|
||||
|
||||
reply = (redisReply*)_reply;
|
||||
if (output_raw_strings) {
|
||||
out = cliFormatReplyRaw(reply);
|
||||
} else {
|
||||
@@ -452,12 +466,17 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
size_t *argvlen;
|
||||
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;
|
||||
}
|
||||
|
||||
output_raw = !strcasecmp(command,"info");
|
||||
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
|
||||
cliOutputHelp(--argc, ++argv);
|
||||
return REDIS_OK;
|
||||
@@ -487,9 +506,21 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
}
|
||||
}
|
||||
|
||||
if (cliReadReply(output_raw) != 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;
|
||||
}
|
||||
|
||||
@@ -522,6 +553,10 @@ 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++;
|
||||
@@ -530,6 +565,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
i++;
|
||||
} 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]);
|
||||
@@ -574,10 +611,13 @@ static void usage() {
|
||||
" -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"
|
||||
@@ -585,6 +625,7 @@ static void usage() {
|
||||
" 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"
|
||||
@@ -607,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;
|
||||
@@ -636,14 +691,25 @@ static void repl() {
|
||||
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) {
|
||||
@@ -652,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() */
|
||||
@@ -675,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;
|
||||
|
||||
@@ -682,33 +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.latency_mode = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.historyfile = NULL;
|
||||
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));
|
||||
}
|
||||
|
||||
+111
-102
@@ -28,6 +28,7 @@
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
#include "slowlog.h"
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#include <execinfo.h>
|
||||
@@ -187,7 +188,9 @@ 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 ============================ */
|
||||
@@ -516,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);
|
||||
@@ -645,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;
|
||||
}
|
||||
|
||||
@@ -673,7 +677,7 @@ 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)
|
||||
@@ -687,6 +691,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
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)
|
||||
@@ -768,7 +773,6 @@ void initServerConfig() {
|
||||
server.syslog_enabled = 0;
|
||||
server.syslog_ident = zstrdup("redis");
|
||||
server.syslog_facility = LOG_LOCAL0;
|
||||
server.glueoutputbuf = 1;
|
||||
server.daemonize = 0;
|
||||
server.appendonly = 0;
|
||||
server.appendfsync = APPENDFSYNC_EVERYSEC;
|
||||
@@ -829,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() {
|
||||
@@ -836,7 +844,7 @@ 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,
|
||||
@@ -851,10 +859,13 @@ void initServer() {
|
||||
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 */
|
||||
@@ -891,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;
|
||||
@@ -911,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
|
||||
@@ -944,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))
|
||||
@@ -970,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
|
||||
@@ -983,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;
|
||||
}
|
||||
@@ -1008,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'");
|
||||
@@ -1018,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;
|
||||
}
|
||||
@@ -1028,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");
|
||||
@@ -1036,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;
|
||||
}
|
||||
@@ -1059,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
|
||||
@@ -1080,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;
|
||||
}
|
||||
|
||||
@@ -1141,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(),
|
||||
@@ -1158,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"
|
||||
@@ -1177,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"
|
||||
@@ -1200,6 +1240,7 @@ 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),
|
||||
lol, bib,
|
||||
server.bpop_blocked_clients,
|
||||
zmalloc_used_memory(),
|
||||
hmem,
|
||||
@@ -1219,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,
|
||||
@@ -1311,6 +1353,7 @@ sds genRedisInfoString(void) {
|
||||
eta
|
||||
);
|
||||
}
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
long long keys, vkeys;
|
||||
|
||||
@@ -1435,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! ================================ */
|
||||
@@ -1504,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);
|
||||
}
|
||||
}
|
||||
@@ -1578,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__)
|
||||
@@ -1609,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;
|
||||
@@ -1648,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 */
|
||||
|
||||
+22
-13
@@ -49,11 +49,8 @@
|
||||
#define REDIS_SHARED_INTEGERS 10000
|
||||
#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
|
||||
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
|
||||
|
||||
/* 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_SLOWLOG_LOG_SLOWER_THAN 10000
|
||||
#define REDIS_SLOWLOG_MAX_LEN 64
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
@@ -86,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
|
||||
@@ -146,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
|
||||
@@ -194,8 +194,8 @@
|
||||
#define APPENDFSYNC_EVERYSEC 2
|
||||
|
||||
/* Zip structure related defaults */
|
||||
#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 64
|
||||
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
|
||||
#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
|
||||
@@ -314,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 */
|
||||
@@ -387,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;
|
||||
@@ -640,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);
|
||||
@@ -662,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, ...)
|
||||
@@ -694,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);
|
||||
|
||||
@@ -779,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();
|
||||
@@ -813,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);
|
||||
@@ -863,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);
|
||||
@@ -1010,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));
|
||||
|
||||
+50
-3
@@ -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. */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -412,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:
|
||||
*
|
||||
@@ -441,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++;
|
||||
|
||||
+115
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
+53
-16
@@ -514,7 +514,12 @@ void lrangeCommand(redisClient *c) {
|
||||
p = ziplistNext(o->ptr,p);
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
listNode *ln = listIndex(o->ptr,start);
|
||||
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);
|
||||
@@ -635,17 +640,37 @@ void lremCommand(redisClient *c) {
|
||||
* as well. This command was originally proposed by Ezra Zygmuntowicz.
|
||||
*/
|
||||
|
||||
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
|
||||
if (!handleClientsWaitingListPush(c,dstkey,value)) {
|
||||
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);
|
||||
server.dirty++;
|
||||
}
|
||||
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. */
|
||||
@@ -661,16 +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);
|
||||
rpoplpushHandlePush(c,c->argv[2],dobj,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++;
|
||||
}
|
||||
}
|
||||
@@ -772,8 +803,10 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
/* Cleanup the client structure */
|
||||
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_BLOCKED;
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
server.bpop_blocked_clients--;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
@@ -814,26 +847,27 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
|
||||
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);
|
||||
redisAssert(ln != listFirst(clients));
|
||||
|
||||
if (dstkey == NULL) {
|
||||
/* BRPOP/BLPOP */
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
addReplyBulk(receiver,key);
|
||||
addReplyBulk(receiver,ele);
|
||||
return 1;
|
||||
return 1; /* Serve just the first client as in B[RL]POP semantics */
|
||||
} else {
|
||||
/* BRPOPLPUSH */
|
||||
/* BRPOPLPUSH, note that receiver->db is always equal to c->db. */
|
||||
dstobj = lookupKeyWrite(receiver->db,dstkey);
|
||||
if (dstobj && checkType(receiver,dstobj,REDIS_LIST)) {
|
||||
decrRefCount(dstkey);
|
||||
} else {
|
||||
rpoplpushHandlePush(receiver,dstkey,dstobj,ele);
|
||||
if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) {
|
||||
rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele);
|
||||
decrRefCount(dstkey);
|
||||
return 1;
|
||||
}
|
||||
decrRefCount(dstkey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,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;
|
||||
|
||||
@@ -884,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;
|
||||
@@ -901,6 +937,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
/* Fix the client structure with the original stuff */
|
||||
c->argv = orig_argv;
|
||||
c->argc = orig_argc;
|
||||
c->cmd = orig_cmd;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -940,7 +977,7 @@ void brpoplpushCommand(redisClient *c) {
|
||||
|
||||
/* Blocking against an empty list in a multi state
|
||||
* returns immediately. */
|
||||
addReply(c, shared.nullmultibulk);
|
||||
addReply(c, shared.nullbulk);
|
||||
} else {
|
||||
/* The list is empty and the client blocks. */
|
||||
blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]);
|
||||
|
||||
+12
-3
@@ -325,7 +325,7 @@ void scardCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void spopCommand(redisClient *c) {
|
||||
robj *set, *ele;
|
||||
robj *set, *ele, *aux;
|
||||
int64_t llele;
|
||||
int encoding;
|
||||
|
||||
@@ -334,12 +334,20 @@ void spopCommand(redisClient *c) {
|
||||
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
addReplyBulkLongLong(c,llele);
|
||||
ele = createStringObjectFromLongLong(llele);
|
||||
set->ptr = intsetRemove(set->ptr,llele,NULL);
|
||||
} else {
|
||||
addReplyBulk(c,ele);
|
||||
incrRefCount(ele);
|
||||
setTypeRemove(set,ele);
|
||||
}
|
||||
|
||||
/* 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++;
|
||||
@@ -420,6 +428,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
si = setTypeInitIterator(sets[0]);
|
||||
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 &&
|
||||
|
||||
+9
-4
@@ -1,4 +1,3 @@
|
||||
#include <limits.h>
|
||||
#include "redis.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -26,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) {
|
||||
@@ -260,7 +260,7 @@ void getrangeCommand(redisClient *c) {
|
||||
return;
|
||||
if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
|
||||
return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
@@ -281,7 +281,7 @@ void getrangeCommand(redisClient *c) {
|
||||
/* Precondition: end >= 0 && end < strlen, so the only condition where
|
||||
* nothing can be returned is: start > end. */
|
||||
if (start > end) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReply(c,shared.emptybulk);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
|
||||
}
|
||||
@@ -346,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]);
|
||||
|
||||
+33
-9
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.1.8"
|
||||
#define REDIS_VERSION "2.2.15"
|
||||
|
||||
@@ -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. */
|
||||
|
||||
+38
-33
@@ -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);
|
||||
@@ -875,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) {
|
||||
@@ -897,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;
|
||||
@@ -1233,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;
|
||||
|
||||
@@ -1241,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();
|
||||
@@ -1254,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);
|
||||
}
|
||||
@@ -1293,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
@@ -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
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -44,9 +44,14 @@ proc assert_type {type key} {
|
||||
assert_equal $type [r type $key]
|
||||
}
|
||||
|
||||
# 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 {[info exists ::env(TERM)] && [string match $::env(TERM) xterm]} {
|
||||
if {[color_term]} {
|
||||
puts -nonewline "$s\033\[0K"
|
||||
flush stdout
|
||||
set ::backward_count [string length $s]
|
||||
@@ -55,7 +60,7 @@ proc announce_test {s} {
|
||||
|
||||
# This is called after the test finished
|
||||
proc colored_dot {tags passed} {
|
||||
if {[info exists ::env(TERM)] && [string match $::env(TERM) xterm]} {
|
||||
if {[color_term]} {
|
||||
# Go backward and delete what announc_test function printed.
|
||||
puts -nonewline "\033\[${::backward_count}D\033\[0K\033\[J"
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ proc cleanup {} {
|
||||
}
|
||||
|
||||
proc execute_everything {} {
|
||||
execute_tests "unit/printver"
|
||||
execute_tests "unit/auth"
|
||||
execute_tests "unit/protocol"
|
||||
execute_tests "unit/basic"
|
||||
@@ -124,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"
|
||||
|
||||
+38
-11
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
start_server {} {
|
||||
set i [r info]
|
||||
regexp {redis_version:(.*?)\r\n} $i - version
|
||||
regexp {redis_git_sha1:(.*?)\r\n} $i - sha1
|
||||
puts "Testing Redis version $version ($sha1)"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
start_server {tags {"slowlog"}} {
|
||||
test {SLOWLOG - check that it starts with an empty log} {
|
||||
r slowlog len
|
||||
} {0}
|
||||
|
||||
test {SLOWLOG - only logs commands taking more time than specified} {
|
||||
r config set slowlog-log-slower-than 100000
|
||||
r ping
|
||||
assert_equal [r slowlog len] 0
|
||||
r debug sleep 0.2
|
||||
assert_equal [r slowlog len] 1
|
||||
}
|
||||
|
||||
test {SLOWLOG - max entries is correctly handled} {
|
||||
r config set slowlog-log-slower-than 0
|
||||
r config set slowlog-max-len 10
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
r ping
|
||||
}
|
||||
r slowlog len
|
||||
} {10}
|
||||
|
||||
test {SLOWLOG - GET optional argument to limit output len works} {
|
||||
llength [r slowlog get 5]
|
||||
} {5}
|
||||
|
||||
test {SLOWLOG - RESET subcommand works} {
|
||||
r config set slowlog-log-slower-than 100000
|
||||
r slowlog reset
|
||||
r slowlog len
|
||||
} {0}
|
||||
|
||||
test {SLOWLOG - logged entry sanity check} {
|
||||
r debug sleep 0.2
|
||||
set e [lindex [r slowlog get] 0]
|
||||
assert_equal [llength $e] 4
|
||||
assert_equal [lindex $e 0] 105
|
||||
assert_equal [expr {[lindex $e 2] > 100000}] 1
|
||||
assert_equal [lindex $e 3] {debug sleep 0.2}
|
||||
}
|
||||
}
|
||||
@@ -262,6 +262,14 @@ start_server {
|
||||
r exec
|
||||
} {foo bar {} {} {bar foo}}
|
||||
|
||||
test {BRPOPLPUSH timeout} {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
$rd brpoplpush foo_list bar_list 1
|
||||
after 2000
|
||||
$rd read
|
||||
} {}
|
||||
|
||||
foreach {pop} {BLPOP BRPOP} {
|
||||
test "$pop: with single empty list argument" {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
@@ -484,6 +484,13 @@ start_server {tags {"zset"}} {
|
||||
test {ZINTERSTORE with AGGREGATE MAX} {
|
||||
list [r zinterstore zsetc 2 zseta zsetb aggregate max] [r zrange zsetc 0 -1 withscores]
|
||||
} {2 {b 2 c 3}}
|
||||
|
||||
test {ZINTERSTORE regression with two sets, intset+hashtable} {
|
||||
r del seta setb setc
|
||||
r sadd set1 a
|
||||
r sadd set2 10
|
||||
r zinterstore set3 2 set1 set2
|
||||
} {0}
|
||||
|
||||
foreach cmd {ZUNIONSTORE ZINTERSTORE} {
|
||||
test "$cmd with +inf/-inf scores" {
|
||||
|
||||
@@ -42,6 +42,7 @@ end
|
||||
def commands
|
||||
return @commands if @commands
|
||||
|
||||
require "rubygems"
|
||||
require "net/http"
|
||||
require "net/https"
|
||||
require "json"
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$1" = "" ]
|
||||
then
|
||||
echo "Usage: mktarball.sh <git tag, branch or commit>"
|
||||
echo "Example: mktarball.sh 2.2-rc4"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PREFIX="redis-${1}/"
|
||||
TARBALL="/tmp/redis-${1}.tar.gz"
|
||||
git archive --format=tar --prefix=$PREFIX $1 | gzip -c > $TARBALL
|
||||
echo "File created: $TARBALL"
|
||||
+14
-7
@@ -1,7 +1,11 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Simple Redis init.d script conceived to work on Linux systems
|
||||
# as it does use of the /proc filesystem.
|
||||
|
||||
REDISPORT=6379
|
||||
EXEC=/usr/local/bin/redis-server
|
||||
CLIEXEC=/usr/local/bin/redis-cli
|
||||
|
||||
PIDFILE=/var/run/redis_${REDISPORT}.pid
|
||||
CONF="/etc/redis/${REDISPORT}.conf"
|
||||
@@ -10,21 +14,21 @@ case "$1" in
|
||||
start)
|
||||
if [ -f $PIDFILE ]
|
||||
then
|
||||
echo -n "$PIDFILE exists, process is already running or crashed\n"
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
else
|
||||
echo -n "Starting Redis server...\n"
|
||||
echo "Starting Redis server..."
|
||||
$EXEC $CONF
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo -n "$PIDFILE does not exist, process is not running\n"
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
else
|
||||
PID=$(cat $PIDFILE)
|
||||
echo -n "Stopping ...\n"
|
||||
echo -n "SHUTDOWN\r\n" | nc localhost $REDISPORT &
|
||||
while [ -x /proc/${PIDFILE} ]
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
@@ -32,4 +36,7 @@ case "$1" in
|
||||
echo "Redis stopped"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Please use start or stop as first argument"
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user