Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
2380388974 | ||
|
|
3c49070b35 | ||
|
|
d69a483556 | ||
|
|
f2f2424e00 | ||
|
|
c4e93cadfb | ||
|
|
b902c1413e | ||
|
|
54e9f28922 | ||
|
|
f474a5bd4e | ||
|
|
1cd3c1e08c | ||
|
|
174df6fe49 | ||
|
|
53f1d81712 | ||
|
|
021321e0ef | ||
|
|
076ff11c06 | ||
|
|
4769dc7826 | ||
|
|
039357e471 |
+48
-1
@@ -8,6 +8,53 @@ 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.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)
|
||||
====================================================
|
||||
|
||||
@@ -49,7 +96,7 @@ WHAT'S NEW IN REDIS 2.2 compared to the 2.0 version?
|
||||
* 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.
|
||||
|
||||
@@ -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
+28
-18
@@ -6,32 +6,35 @@ OBJ = net.o hiredis.o sds.o async.o
|
||||
BINS = hiredis-example hiredis-test
|
||||
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
OPTIMIZATION?=-O3
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
|
||||
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF)
|
||||
CCLINK?=-ldl -lnsl -lsocket -lm -lpthread
|
||||
LDFLAGS?=-L. -Wl,-R,.
|
||||
DYLIBNAME?=libhiredis.so
|
||||
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
|
||||
DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
OBJARCH?= -arch i386 -arch x86_64
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?=-lm -pthread
|
||||
LDFLAGS?=-L. -Wl,-rpath,.
|
||||
OBJARCH?=-arch i386 -arch x86_64
|
||||
DYLIBNAME?=libhiredis.dylib
|
||||
DYLIB_MAKE_CMD?=libtool -dynamic -o ${DYLIBNAME} -lm ${DEBUG} - ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=libtool -static -o ${STLIBNAME} - ${OBJ}
|
||||
else
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?=-lm -pthread
|
||||
LDFLAGS?=-L. -Wl,-rpath,.
|
||||
DYLIBNAME?=libhiredis.so
|
||||
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -ggdb
|
||||
CCOPT= $(CFLAGS) $(CCLINK)
|
||||
DEBUG?= -g -ggdb
|
||||
|
||||
PREFIX?= /usr/local
|
||||
INSTALL_INC= $(PREFIX)/include/hiredis
|
||||
@@ -43,8 +46,6 @@ all: ${DYLIBNAME} ${BINS}
|
||||
# Deps (use make dep to generate this)
|
||||
net.o: net.c fmacros.h net.h
|
||||
async.o: async.c async.h hiredis.h sds.h util.h
|
||||
example-libev.o: example-libev.c hiredis.h async.h adapters/libev.h
|
||||
example-libevent.o: example-libevent.c hiredis.h async.h adapters/libevent.h
|
||||
example.o: example.c hiredis.h
|
||||
hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
|
||||
sds.o: sds.c sds.h
|
||||
@@ -60,14 +61,23 @@ dynamic: ${DYLIBNAME}
|
||||
static: ${STLIBNAME}
|
||||
|
||||
# Binaries:
|
||||
hiredis-example-libevent: example-libevent.o ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
|
||||
hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -levent example-libevent.c
|
||||
|
||||
hiredis-example-libev: example-libev.o ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
|
||||
hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -lev example-libev.c
|
||||
|
||||
ifndef AE_DIR
|
||||
hiredis-example-ae:
|
||||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||
@false
|
||||
else
|
||||
hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) -lhiredis example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
|
||||
endif
|
||||
|
||||
hiredis-%: %.o ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -Wl,-rpath,. $<
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis $<
|
||||
|
||||
test: hiredis-test
|
||||
./hiredis-test
|
||||
|
||||
Vendored
+45
-18
@@ -35,16 +35,18 @@ To consume the synchronous API, there are only a few function calls that need to
|
||||
|
||||
### Connecting
|
||||
|
||||
The function `redisConnect` is used to create a so-called `redisContext`. The context is where
|
||||
Hiredis holds state for a connection. The `redisContext` struct has an `error` field that is
|
||||
non-NULL when the connection is in an error state. It contains a string with a textual
|
||||
representation of the error. After trying to connect to Redis using `redisConnect` you should
|
||||
check the `error` field to see if establishing the connection was successful:
|
||||
The function `redisConnect` is used to create a so-called `redisContext`. The
|
||||
context is where Hiredis holds state for a connection. The `redisContext`
|
||||
struct has an integer `err` field that is non-zero when an the connection is in
|
||||
an error state. The field `errstr` will contain a string with a description of
|
||||
the error. More information on errors can be found in the **Errors** section.
|
||||
After trying to connect to Redis using `redisConnect` you should
|
||||
check the `err` field to see if establishing the connection was successful:
|
||||
|
||||
redisContext *c = redisConnect("127.0.0.1", 6379);
|
||||
if (c->error != NULL) {
|
||||
printf("Error: %s\n", c->error);
|
||||
// handle error
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
}
|
||||
|
||||
### Sending commands
|
||||
@@ -76,8 +78,8 @@ anywhere in an argument:
|
||||
### Using replies
|
||||
|
||||
The return value of `redisCommand` holds a reply when the command was
|
||||
successfully executed. When the return value is `NULL`, the `error` field
|
||||
in the context can be used to find out what was the cause of failure.
|
||||
successfully executed. When an error occurs, the return value is `NULL` and
|
||||
the `err` field in the context will be set (see section on **Errors**).
|
||||
Once an error is returned the context cannot be reused and you should set up
|
||||
a new connection.
|
||||
|
||||
@@ -166,7 +168,7 @@ to the `redisCommand` family, apart from not returning a reply:
|
||||
After calling either function one or more times, `redisGetReply` can be used to receive the
|
||||
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
|
||||
the latter means an error occurred while reading a reply. Just as with the other commands,
|
||||
the `error` field in the context can be used to find out what the cause of this error is.
|
||||
the `err` field in the context can be used to find out what the cause of this error is.
|
||||
|
||||
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
|
||||
a single call to `write(2)`):
|
||||
@@ -184,10 +186,35 @@ This API can also be used to implement a blocking subscriber:
|
||||
reply = redisCommand(context,"SUBSCRIBE foo");
|
||||
freeReplyObject(reply);
|
||||
while(redisGetReply(context,&reply) == REDIS_OK) {
|
||||
// consume message
|
||||
freeReplyObject(reply);
|
||||
// consume message
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
### Errors
|
||||
|
||||
When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
|
||||
returned. The `err` field inside the context will be non-zero and set to one of the
|
||||
following constants:
|
||||
|
||||
* **`REDIS_ERR_IO`**:
|
||||
There was an I/O error while creating the connection, trying to write
|
||||
to the socket or read from the socket. If you included `errno.h` in your
|
||||
application, you can use the global `errno` variable to find out what is
|
||||
wrong.
|
||||
|
||||
* **`REDIS_ERR_EOF`**:
|
||||
The server closed the connection which resulted in an empty read.
|
||||
|
||||
* **`REDIS_ERR_PROTOCOL`**:
|
||||
There was an error while parsing the protocol.
|
||||
|
||||
* **`REDIS_ERR_OTHER`**:
|
||||
Any other error. Currently, it is only used when a specified hostname to connect
|
||||
to cannot be resolved.
|
||||
|
||||
In every case, the `errstr` field in the context will be set to hold a string representation
|
||||
of the error.
|
||||
|
||||
## Asynchronous API
|
||||
|
||||
Hiredis comes with an asynchronous API that works easily with any event library.
|
||||
@@ -197,15 +224,15 @@ and [libevent](http://monkey.org/~provos/libevent/).
|
||||
### Connecting
|
||||
|
||||
The function `redisAsyncConnect` can be used to establish a non-blocking connection to
|
||||
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `error` field
|
||||
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
|
||||
should be checked after creation to see if there were errors creating the connection.
|
||||
Because the connection that will be created is non-blocking, the kernel is not able to
|
||||
instantly return if the specified host and port is able to accept a connection.
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->error != NULL) {
|
||||
printf("Error: %s\n", c->error);
|
||||
// handle error
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
}
|
||||
|
||||
The asynchronous context can hold a disconnect callback function that is called when the
|
||||
@@ -215,7 +242,7 @@ have the following prototype:
|
||||
void(const redisAsyncContext *c, int status);
|
||||
|
||||
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
|
||||
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `error`
|
||||
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
|
||||
field in the context can be accessed to find out the cause of the error.
|
||||
|
||||
The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
|
||||
|
||||
Vendored
+95
@@ -0,0 +1,95 @@
|
||||
#include <sys/types.h>
|
||||
#include <ae.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisAeEvents {
|
||||
redisAsyncContext *context;
|
||||
aeEventLoop *loop;
|
||||
int fd;
|
||||
int reading, writing;
|
||||
} redisAeEvents;
|
||||
|
||||
void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
((void)el); ((void)fd); ((void)mask);
|
||||
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
((void)el); ((void)fd); ((void)mask);
|
||||
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisAeAddRead(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (!e->reading) {
|
||||
e->reading = 1;
|
||||
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeDelRead(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (e->reading) {
|
||||
e->reading = 0;
|
||||
aeDeleteFileEvent(loop,e->fd,AE_READABLE);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeAddWrite(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (!e->writing) {
|
||||
e->writing = 1;
|
||||
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeDelWrite(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (e->writing) {
|
||||
e->writing = 0;
|
||||
aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeCleanup(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
redisAeDelRead(privdata);
|
||||
redisAeDelWrite(privdata);
|
||||
free(e);
|
||||
}
|
||||
|
||||
int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisAeEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->_adapter_data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = (redisAeEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
e->loop = loop;
|
||||
e->fd = c->fd;
|
||||
e->reading = e->writing = 0;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->evAddRead = redisAeAddRead;
|
||||
ac->evDelRead = redisAeDelRead;
|
||||
ac->evAddWrite = redisAeAddWrite;
|
||||
ac->evDelWrite = redisAeDelWrite;
|
||||
ac->evCleanup = redisAeCleanup;
|
||||
ac->_adapter_data = e;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
Vendored
+40
-19
@@ -10,69 +10,89 @@ typedef struct redisLibevEvents {
|
||||
ev_io rev, wev;
|
||||
} redisLibevEvents;
|
||||
|
||||
void redisLibevReadEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
((void)loop); ((void)revents);
|
||||
redisLibevEvents *e = watcher->data;
|
||||
void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
|
||||
#if EV_MULTIPLICITY
|
||||
((void)loop);
|
||||
#endif
|
||||
((void)revents);
|
||||
|
||||
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisLibevWriteEvent(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
((void)loop); ((void)revents);
|
||||
redisLibevEvents *e = watcher->data;
|
||||
void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
|
||||
#if EV_MULTIPLICITY
|
||||
((void)loop);
|
||||
#endif
|
||||
((void)revents);
|
||||
|
||||
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisLibevAddRead(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (!e->reading) {
|
||||
e->reading = 1;
|
||||
ev_io_start(e->loop,&e->rev);
|
||||
ev_io_start(EV_A_ &e->rev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevDelRead(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (e->reading) {
|
||||
e->reading = 0;
|
||||
ev_io_stop(e->loop,&e->rev);
|
||||
ev_io_stop(EV_A_ &e->rev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevAddWrite(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (!e->writing) {
|
||||
e->writing = 1;
|
||||
ev_io_start(e->loop,&e->wev);
|
||||
ev_io_start(EV_A_ &e->wev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevDelWrite(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (e->writing) {
|
||||
e->writing = 0;
|
||||
ev_io_stop(e->loop,&e->wev);
|
||||
ev_io_stop(EV_A_ &e->wev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevCleanup(void *privdata) {
|
||||
redisLibevEvents *e = privdata;
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
redisLibevDelRead(privdata);
|
||||
redisLibevDelWrite(privdata);
|
||||
free(e);
|
||||
}
|
||||
|
||||
int redisLibevAttach(redisAsyncContext *ac, struct ev_loop *loop) {
|
||||
int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisLibevEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->data != NULL)
|
||||
if (ac->_adapter_data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = malloc(sizeof(*e));
|
||||
e = (redisLibevEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
#if EV_MULTIPLICITY
|
||||
e->loop = loop;
|
||||
#else
|
||||
e->loop = NULL;
|
||||
#endif
|
||||
e->reading = e->writing = 0;
|
||||
e->rev.data = e;
|
||||
e->wev.data = e;
|
||||
@@ -83,10 +103,11 @@ int redisLibevAttach(redisAsyncContext *ac, struct ev_loop *loop) {
|
||||
ac->evAddWrite = redisLibevAddWrite;
|
||||
ac->evDelWrite = redisLibevDelWrite;
|
||||
ac->evCleanup = redisLibevCleanup;
|
||||
ac->data = e;
|
||||
ac->_adapter_data = e;
|
||||
|
||||
/* Initialize read/write events */
|
||||
ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
|
||||
ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+10
-10
@@ -10,38 +10,38 @@ typedef struct redisLibeventEvents {
|
||||
|
||||
void redisLibeventReadEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = arg;
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisLibeventWriteEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = arg;
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisLibeventAddRead(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(&e->rev,NULL);
|
||||
}
|
||||
|
||||
void redisLibeventDelRead(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->rev);
|
||||
}
|
||||
|
||||
void redisLibeventAddWrite(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(&e->wev,NULL);
|
||||
}
|
||||
|
||||
void redisLibeventDelWrite(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->wev);
|
||||
}
|
||||
|
||||
void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = privdata;
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->rev);
|
||||
event_del(&e->wev);
|
||||
free(e);
|
||||
@@ -52,11 +52,11 @@ int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
redisLibeventEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->data != NULL)
|
||||
if (ac->_adapter_data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = malloc(sizeof(*e));
|
||||
e = (redisLibeventEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
@@ -65,7 +65,7 @@ int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
ac->evAddWrite = redisLibeventAddWrite;
|
||||
ac->evDelWrite = redisLibeventDelWrite;
|
||||
ac->evCleanup = redisLibeventCleanup;
|
||||
ac->data = e;
|
||||
ac->_adapter_data = e;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
|
||||
|
||||
Vendored
+46
-9
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -38,8 +40,29 @@ void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
|
||||
|
||||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
|
||||
/* Set all bytes in the async part of the context to 0 */
|
||||
memset(ac+sizeof(redisContext),0,sizeof(redisAsyncContext)-sizeof(redisContext));
|
||||
c = &(ac->c);
|
||||
|
||||
/* The regular connect functions will always set the flag REDIS_CONNECTED.
|
||||
* For the async API, we want to wait until the first write event is
|
||||
* received up before setting this flag, so reset it here. */
|
||||
c->flags &= ~REDIS_CONNECTED;
|
||||
|
||||
ac->err = 0;
|
||||
ac->errstr = NULL;
|
||||
ac->data = NULL;
|
||||
ac->_adapter_data = NULL;
|
||||
|
||||
ac->evAddRead = NULL;
|
||||
ac->evDelRead = NULL;
|
||||
ac->evAddWrite = NULL;
|
||||
ac->evDelWrite = NULL;
|
||||
ac->evCleanup = NULL;
|
||||
|
||||
ac->onConnect = NULL;
|
||||
ac->onDisconnect = NULL;
|
||||
|
||||
ac->replies.head = NULL;
|
||||
ac->replies.tail = NULL;
|
||||
return ac;
|
||||
}
|
||||
|
||||
@@ -70,6 +93,14 @@ int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFun
|
||||
return redisSetReplyObjectFunctions(c,fn);
|
||||
}
|
||||
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
||||
if (ac->onConnect == NULL) {
|
||||
ac->onConnect = fn;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
|
||||
if (ac->onDisconnect == NULL) {
|
||||
ac->onDisconnect = fn;
|
||||
@@ -153,7 +184,7 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
}
|
||||
|
||||
/* Signal event lib to clean up */
|
||||
if (ac->evCleanup) ac->evCleanup(ac->data);
|
||||
if (ac->evCleanup) ac->evCleanup(ac->_adapter_data);
|
||||
|
||||
/* Execute callback with proper status */
|
||||
if (ac->onDisconnect) ac->onDisconnect(ac,status);
|
||||
@@ -206,7 +237,7 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Always re-schedule reads */
|
||||
if (ac->evAddRead) ac->evAddRead(ac->data);
|
||||
if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
}
|
||||
@@ -220,13 +251,19 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
} else {
|
||||
/* Continue writing when not done, stop writing otherwise */
|
||||
if (!done) {
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->data);
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
|
||||
} else {
|
||||
if (ac->evDelWrite) ac->evDelWrite(ac->data);
|
||||
if (ac->evDelWrite) ac->evDelWrite(ac->_adapter_data);
|
||||
}
|
||||
|
||||
/* Always schedule reads when something was written */
|
||||
if (ac->evAddRead) ac->evAddRead(ac->data);
|
||||
/* Always schedule reads after writes */
|
||||
if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
|
||||
|
||||
/* Fire onConnect when this is the first write event. */
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
if (ac->onConnect) ac->onConnect(ac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +286,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
__redisPushCallback(&ac->replies,&cb);
|
||||
|
||||
/* Always schedule a write when the write buffer is non-empty */
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->data);
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
Vendored
+20
-2
@@ -31,6 +31,10 @@
|
||||
#define __HIREDIS_ASYNC_H
|
||||
#include "hiredis.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
|
||||
|
||||
/* Reply callback prototype and container */
|
||||
@@ -46,8 +50,9 @@ typedef struct redisCallbackList {
|
||||
redisCallback *head, *tail;
|
||||
} redisCallbackList;
|
||||
|
||||
/* Disconnect callback prototype */
|
||||
/* Connection callback prototypes */
|
||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void (redisConnectCallback)(const struct redisAsyncContext*);
|
||||
|
||||
/* Context for an async connection to Redis */
|
||||
typedef struct redisAsyncContext {
|
||||
@@ -58,6 +63,12 @@ typedef struct redisAsyncContext {
|
||||
int err;
|
||||
char *errstr;
|
||||
|
||||
/* Not used by hiredis */
|
||||
void *data;
|
||||
|
||||
/* Used by the different event lib adapters to store their private data */
|
||||
void *_adapter_data;
|
||||
|
||||
/* Called when the library expects to start reading/writing.
|
||||
* The supplied functions should be idempotent. */
|
||||
void (*evAddRead)(void *privdata);
|
||||
@@ -65,12 +76,14 @@ typedef struct redisAsyncContext {
|
||||
void (*evAddWrite)(void *privdata);
|
||||
void (*evDelWrite)(void *privdata);
|
||||
void (*evCleanup)(void *privdata);
|
||||
void *data;
|
||||
|
||||
/* Called when either the connection is terminated due to an error or per
|
||||
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
|
||||
redisDisconnectCallback *onDisconnect;
|
||||
|
||||
/* Called when the first write event was received. */
|
||||
redisConnectCallback *onConnect;
|
||||
|
||||
/* Reply callbacks */
|
||||
redisCallbackList replies;
|
||||
} redisAsyncContext;
|
||||
@@ -78,6 +91,7 @@ typedef struct redisAsyncContext {
|
||||
/* Functions that proxy to hiredis */
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn);
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
|
||||
@@ -91,4 +105,8 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
|
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "adapters/ae.h"
|
||||
|
||||
/* Put event loop in the global scope, so it can be explicitly stopped */
|
||||
static aeEventLoop *loop;
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c) {
|
||||
((void)c);
|
||||
printf("connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
printf("disconnected...\n");
|
||||
aeStop(loop);
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
loop = aeCreateEventLoop();
|
||||
redisAeAttach(loop, c);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
aeMain(loop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vendored
+9
-3
@@ -15,15 +15,20 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c) {
|
||||
((void)c);
|
||||
printf("connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
printf("disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct ev_loop *loop = ev_default_loop(0);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
@@ -32,10 +37,11 @@ int main (int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisLibevAttach(c,loop);
|
||||
redisLibevAttach(EV_DEFAULT_ c);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
ev_loop(loop, 0);
|
||||
ev_loop(EV_DEFAULT_ 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vendored
+7
@@ -15,10 +15,16 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c) {
|
||||
((void)c);
|
||||
printf("connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
printf("disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
@@ -33,6 +39,7 @@ int main (int argc, char **argv) {
|
||||
}
|
||||
|
||||
redisLibeventAttach(c,base);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
|
||||
Vendored
+1
-1
@@ -17,7 +17,7 @@ int main(void) {
|
||||
|
||||
/* PING server */
|
||||
reply = redisCommand(c,"PING");
|
||||
printf("PONG: %s\n", reply->str);
|
||||
printf("PING: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key */
|
||||
|
||||
Vendored
+186
-55
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -32,6 +34,7 @@
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "net.h"
|
||||
@@ -44,10 +47,12 @@ typedef struct redisReader {
|
||||
void *reply; /* holds temporary reply */
|
||||
|
||||
sds buf; /* read buffer */
|
||||
unsigned int pos; /* buffer cursor */
|
||||
size_t pos; /* buffer cursor */
|
||||
size_t len; /* buffer length */
|
||||
|
||||
redisReadTask rstack[3]; /* stack of read tasks */
|
||||
int ridx; /* index of stack */
|
||||
void *privdata; /* user-settable arbitrary field */
|
||||
} redisReader;
|
||||
|
||||
static redisReply *createReplyObject(int type);
|
||||
@@ -68,7 +73,7 @@ static redisReplyObjectFunctions defaultFunctions = {
|
||||
|
||||
/* Create a reply object */
|
||||
static redisReply *createReplyObject(int type) {
|
||||
redisReply *r = calloc(sizeof(*r),1);
|
||||
redisReply *r = malloc(sizeof(*r));
|
||||
|
||||
if (!r) redisOOM();
|
||||
r->type = type;
|
||||
@@ -88,9 +93,10 @@ void freeReplyObject(void *reply) {
|
||||
if (r->element[j]) freeReplyObject(r->element[j]);
|
||||
free(r->element);
|
||||
break;
|
||||
default:
|
||||
if (r->str != NULL)
|
||||
free(r->str);
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_STRING:
|
||||
free(r->str);
|
||||
break;
|
||||
}
|
||||
free(r);
|
||||
@@ -111,7 +117,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
r->len = len;
|
||||
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
redisReply *parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
@@ -124,7 +130,7 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL)
|
||||
redisOOM();
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
redisReply *parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
@@ -135,7 +141,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
redisReply *r = createReplyObject(REDIS_REPLY_INTEGER);
|
||||
r->integer = value;
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
redisReply *parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
@@ -145,7 +151,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
static void *createNilObject(const redisReadTask *task) {
|
||||
redisReply *r = createReplyObject(REDIS_REPLY_NIL);
|
||||
if (task->parent) {
|
||||
redisReply *parent = task->parent;
|
||||
redisReply *parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
@@ -154,7 +160,7 @@ static void *createNilObject(const redisReadTask *task) {
|
||||
|
||||
static char *readBytes(redisReader *r, unsigned int bytes) {
|
||||
char *p;
|
||||
if (sdslen(r->buf)-r->pos >= bytes) {
|
||||
if (r->len-r->pos >= bytes) {
|
||||
p = r->buf+r->pos;
|
||||
r->pos += bytes;
|
||||
return p;
|
||||
@@ -162,20 +168,60 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *seekNewline(char *s) {
|
||||
/* Find pointer to \r\n without strstr */
|
||||
while (s != NULL) {
|
||||
s = strchr(s,'\r');
|
||||
if (s != NULL) {
|
||||
if (s[1] == '\n')
|
||||
break;
|
||||
else
|
||||
s++;
|
||||
/* Find pointer to \r\n. */
|
||||
static char *seekNewline(char *s, size_t len) {
|
||||
int pos = 0;
|
||||
int _len = len-1;
|
||||
|
||||
/* Position should be < len-1 because the character at "pos" should be
|
||||
* followed by a \n. Note that strchr cannot be used because it doesn't
|
||||
* allow to search a limited length and the buffer that is being searched
|
||||
* might not have a trailing NULL character. */
|
||||
while (pos < _len) {
|
||||
while(pos < _len && s[pos] != '\r') pos++;
|
||||
if (s[pos] != '\r') {
|
||||
/* Not found. */
|
||||
return NULL;
|
||||
} else {
|
||||
break;
|
||||
if (s[pos+1] == '\n') {
|
||||
/* Found. */
|
||||
return s+pos;
|
||||
} else {
|
||||
/* Continue searching. */
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read a long long value starting at *s, under the assumption that it will be
|
||||
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
|
||||
static long long readLongLong(char *s) {
|
||||
long long v = 0;
|
||||
int dec, mult = 1;
|
||||
char c;
|
||||
|
||||
if (*s == '-') {
|
||||
mult = -1;
|
||||
s++;
|
||||
} else if (*s == '+') {
|
||||
mult = 1;
|
||||
s++;
|
||||
}
|
||||
|
||||
while ((c = *(s++)) != '\r') {
|
||||
dec = c - '0';
|
||||
if (dec >= 0 && dec < 10) {
|
||||
v *= 10;
|
||||
v += dec;
|
||||
} else {
|
||||
/* Should not happen... */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return mult*v;
|
||||
}
|
||||
|
||||
static char *readLine(redisReader *r, int *_len) {
|
||||
@@ -183,7 +229,7 @@ static char *readLine(redisReader *r, int *_len) {
|
||||
int len;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p);
|
||||
s = seekNewline(p,(r->len-r->pos));
|
||||
if (s != NULL) {
|
||||
len = s-(r->buf+r->pos);
|
||||
r->pos += len+2; /* skip \r\n */
|
||||
@@ -227,7 +273,7 @@ static int processLineItem(redisReader *r) {
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (r->fn) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
obj = r->fn->createInteger(cur,strtoll(p,NULL,10));
|
||||
obj = r->fn->createInteger(cur,readLongLong(p));
|
||||
} else {
|
||||
obj = r->fn->createString(cur,p,len);
|
||||
}
|
||||
@@ -235,9 +281,8 @@ static int processLineItem(redisReader *r) {
|
||||
obj = (void*)(size_t)(cur->type);
|
||||
}
|
||||
|
||||
/* If there is no root yet, register this object as root. */
|
||||
if (r->reply == NULL)
|
||||
r->reply = obj;
|
||||
/* Set reply if this is the root object. */
|
||||
if (r->ridx == 0) r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return 0;
|
||||
}
|
||||
@@ -250,32 +295,36 @@ static int processBulkItem(redisReader *r) {
|
||||
char *p, *s;
|
||||
long len;
|
||||
unsigned long bytelen;
|
||||
int success = 0;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p);
|
||||
s = seekNewline(p,r->len-r->pos);
|
||||
if (s != NULL) {
|
||||
p = r->buf+r->pos;
|
||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||
len = strtol(p,NULL,10);
|
||||
len = readLongLong(p);
|
||||
|
||||
if (len < 0) {
|
||||
/* The nil object can always be created. */
|
||||
obj = r->fn ? r->fn->createNil(cur) :
|
||||
(void*)REDIS_REPLY_NIL;
|
||||
success = 1;
|
||||
} else {
|
||||
/* Only continue when the buffer contains the entire bulk item. */
|
||||
bytelen += len+2; /* include \r\n */
|
||||
if (r->pos+bytelen <= sdslen(r->buf)) {
|
||||
if (r->pos+bytelen <= r->len) {
|
||||
obj = r->fn ? r->fn->createString(cur,s+2,len) :
|
||||
(void*)REDIS_REPLY_STRING;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Proceed when obj was created. */
|
||||
if (obj != NULL) {
|
||||
if (success) {
|
||||
r->pos += bytelen;
|
||||
if (r->reply == NULL)
|
||||
r->reply = obj;
|
||||
|
||||
/* Set reply if this is the root object. */
|
||||
if (r->ridx == 0) r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return 0;
|
||||
}
|
||||
@@ -288,9 +337,19 @@ static int processMultiBulkItem(redisReader *r) {
|
||||
void *obj;
|
||||
char *p;
|
||||
long elements;
|
||||
int root = 0;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 1 */
|
||||
if (r->ridx == 2) {
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"No support for nested multi bulk replies with depth > 1"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,NULL)) != NULL) {
|
||||
elements = strtol(p,NULL,10);
|
||||
elements = readLongLong(p);
|
||||
root = (r->ridx == 0);
|
||||
|
||||
if (elements == -1) {
|
||||
obj = r->fn ? r->fn->createNil(cur) :
|
||||
(void*)REDIS_REPLY_NIL;
|
||||
@@ -302,19 +361,21 @@ static int processMultiBulkItem(redisReader *r) {
|
||||
/* Modify task stack when there are more than 0 elements. */
|
||||
if (elements > 0) {
|
||||
cur->elements = elements;
|
||||
cur->obj = obj;
|
||||
r->ridx++;
|
||||
r->rstack[r->ridx].type = -1;
|
||||
r->rstack[r->ridx].elements = -1;
|
||||
r->rstack[r->ridx].parent = obj;
|
||||
r->rstack[r->ridx].idx = 0;
|
||||
r->rstack[r->ridx].obj = NULL;
|
||||
r->rstack[r->ridx].parent = cur;
|
||||
r->rstack[r->ridx].privdata = r->privdata;
|
||||
} else {
|
||||
moveToNextTask(r);
|
||||
}
|
||||
}
|
||||
|
||||
/* Object was created, so we can always continue. */
|
||||
if (r->reply == NULL)
|
||||
r->reply = obj;
|
||||
/* Set reply if this is the root object. */
|
||||
if (root) r->reply = obj;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
@@ -347,7 +408,7 @@ static int processItem(redisReader *r) {
|
||||
default:
|
||||
byte = sdscatrepr(sdsempty(),p,1);
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"protocol error, got %s as reply type byte", byte));
|
||||
"Protocol error, got %s as reply type byte", byte));
|
||||
sdsfree(byte);
|
||||
return -1;
|
||||
}
|
||||
@@ -368,8 +429,7 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_ARRAY:
|
||||
return processMultiBulkItem(r);
|
||||
default:
|
||||
redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
|
||||
"unknown item type '%d'", cur->type));
|
||||
assert(NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -394,6 +454,17 @@ int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFuncti
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Set the private data field that is used in the read tasks. This argument can
|
||||
* be used to curry arbitrary data to the custom reply object functions. */
|
||||
int redisReplyReaderSetPrivdata(void *reader, void *privdata) {
|
||||
redisReader *r = reader;
|
||||
if (r->reply == NULL) {
|
||||
r->privdata = privdata;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* External libraries wrapping hiredis might need access to the temporary
|
||||
* variable while the reply is built up. When the reader contains an
|
||||
* object in between receiving some bytes to parse, this object might
|
||||
@@ -437,8 +508,10 @@ void redisReplyReaderFeed(void *reader, char *buf, size_t len) {
|
||||
redisReader *r = reader;
|
||||
|
||||
/* Copy the provided buffer. */
|
||||
if (buf != NULL && len >= 1)
|
||||
if (buf != NULL && len >= 1) {
|
||||
r->buf = sdscatlen(r->buf,buf,len);
|
||||
r->len = sdslen(r->buf);
|
||||
}
|
||||
}
|
||||
|
||||
int redisReplyReaderGetReply(void *reader, void **reply) {
|
||||
@@ -446,15 +519,17 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
|
||||
if (reply != NULL) *reply = NULL;
|
||||
|
||||
/* When the buffer is empty, there will never be a reply. */
|
||||
if (sdslen(r->buf) == 0)
|
||||
if (r->len == 0)
|
||||
return REDIS_OK;
|
||||
|
||||
/* Set first item to process when the stack is empty. */
|
||||
if (r->ridx == -1) {
|
||||
r->rstack[0].type = -1;
|
||||
r->rstack[0].elements = -1;
|
||||
r->rstack[0].parent = NULL;
|
||||
r->rstack[0].idx = -1;
|
||||
r->rstack[0].obj = NULL;
|
||||
r->rstack[0].parent = NULL;
|
||||
r->rstack[0].privdata = r->privdata;
|
||||
r->ridx = 0;
|
||||
}
|
||||
|
||||
@@ -465,14 +540,15 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
|
||||
|
||||
/* Discard the consumed part of the buffer. */
|
||||
if (r->pos > 0) {
|
||||
if (r->pos == sdslen(r->buf)) {
|
||||
if (r->pos == r->len) {
|
||||
/* sdsrange has a quirck on this edge case. */
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
} else {
|
||||
r->buf = sdsrange(r->buf,r->pos,sdslen(r->buf));
|
||||
r->buf = sdsrange(r->buf,r->pos,r->len);
|
||||
}
|
||||
r->pos = 0;
|
||||
r->len = sdslen(r->buf);
|
||||
}
|
||||
|
||||
/* Emit a reply when there is one. */
|
||||
@@ -481,7 +557,7 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
|
||||
r->reply = NULL;
|
||||
|
||||
/* Destroy the buffer when it is empty and is quite large. */
|
||||
if (sdslen(r->buf) == 0 && sdsavail(r->buf) > 16*1024) {
|
||||
if (r->len == 0 && sdsavail(r->buf) > 16*1024) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
r->pos = 0;
|
||||
@@ -525,6 +601,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
char *cmd = NULL; /* final command */
|
||||
int pos; /* position in final command */
|
||||
sds current; /* current argument */
|
||||
int interpolated = 0; /* did we do interpolation on an argument? */
|
||||
char **argv = NULL;
|
||||
int argc = 0, j;
|
||||
int totlen = 0;
|
||||
@@ -541,6 +618,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
if (sdslen(current) != 0) {
|
||||
addArgument(current, &argv, &argc, &totlen);
|
||||
current = sdsempty();
|
||||
interpolated = 0;
|
||||
}
|
||||
} else {
|
||||
current = sdscatlen(current,c,1);
|
||||
@@ -549,16 +627,74 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
switch(c[1]) {
|
||||
case 's':
|
||||
arg = va_arg(ap,char*);
|
||||
current = sdscat(current,arg);
|
||||
size = strlen(arg);
|
||||
if (size > 0)
|
||||
current = sdscatlen(current,arg,size);
|
||||
interpolated = 1;
|
||||
break;
|
||||
case 'b':
|
||||
arg = va_arg(ap,char*);
|
||||
size = va_arg(ap,size_t);
|
||||
current = sdscatlen(current,arg,size);
|
||||
if (size > 0)
|
||||
current = sdscatlen(current,arg,size);
|
||||
interpolated = 1;
|
||||
break;
|
||||
case '%':
|
||||
cmd = sdscat(cmd,"%");
|
||||
current = sdscat(current,"%");
|
||||
break;
|
||||
default:
|
||||
/* Try to detect printf format */
|
||||
{
|
||||
char _format[16];
|
||||
const char *_p = c+1;
|
||||
size_t _l = 0;
|
||||
va_list _cpy;
|
||||
|
||||
/* Flags */
|
||||
if (*_p != '\0' && *_p == '#') _p++;
|
||||
if (*_p != '\0' && *_p == '0') _p++;
|
||||
if (*_p != '\0' && *_p == '-') _p++;
|
||||
if (*_p != '\0' && *_p == ' ') _p++;
|
||||
if (*_p != '\0' && *_p == '+') _p++;
|
||||
|
||||
/* Field width */
|
||||
while (*_p != '\0' && isdigit(*_p)) _p++;
|
||||
|
||||
/* Precision */
|
||||
if (*_p == '.') {
|
||||
_p++;
|
||||
while (*_p != '\0' && isdigit(*_p)) _p++;
|
||||
}
|
||||
|
||||
/* Modifiers */
|
||||
if (*_p != '\0') {
|
||||
if (*_p == 'h' || *_p == 'l') {
|
||||
/* Allow a single repetition for these modifiers */
|
||||
if (_p[0] == _p[1]) _p++;
|
||||
_p++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Conversion specifier */
|
||||
if (*_p != '\0' && strchr("diouxXeEfFgGaA",*_p) != NULL) {
|
||||
_l = (_p+1)-c;
|
||||
if (_l < sizeof(_format)-2) {
|
||||
memcpy(_format,c,_l);
|
||||
_format[_l] = '\0';
|
||||
va_copy(_cpy,ap);
|
||||
current = sdscatvprintf(current,_format,_cpy);
|
||||
interpolated = 1;
|
||||
va_end(_cpy);
|
||||
|
||||
/* Update current position (note: outer blocks
|
||||
* increment c twice so compensate here) */
|
||||
c = _p-1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Consume and discard vararg */
|
||||
va_arg(ap,void);
|
||||
}
|
||||
}
|
||||
c++;
|
||||
}
|
||||
@@ -566,7 +702,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
}
|
||||
|
||||
/* Add the last argument if needed */
|
||||
if (sdslen(current) != 0) {
|
||||
if (interpolated || sdslen(current) != 0) {
|
||||
addArgument(current, &argv, &argc, &totlen);
|
||||
} else {
|
||||
sdsfree(current);
|
||||
@@ -664,7 +800,6 @@ void __redisSetError(redisContext *c, int type, const sds errstr) {
|
||||
|
||||
static redisContext *redisContextInit() {
|
||||
redisContext *c = calloc(sizeof(redisContext),1);
|
||||
c->fd = -1; /* quick fix for a bug that should be addressed differently */
|
||||
c->err = 0;
|
||||
c->errstr = NULL;
|
||||
c->obuf = sdsempty();
|
||||
@@ -692,7 +827,6 @@ void redisFree(redisContext *c) {
|
||||
redisContext *redisConnect(const char *ip, int port) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags |= REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectTcp(c,ip,port);
|
||||
return c;
|
||||
}
|
||||
@@ -700,7 +834,6 @@ redisContext *redisConnect(const char *ip, int port) {
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectTcp(c,ip,port);
|
||||
return c;
|
||||
}
|
||||
@@ -708,7 +841,6 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||
redisContext *redisConnectUnix(const char *path) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags |= REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectUnix(c,path);
|
||||
return c;
|
||||
}
|
||||
@@ -716,7 +848,6 @@ redisContext *redisConnectUnix(const char *path) {
|
||||
redisContext *redisConnectUnixNonBlock(const char *path) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
redisContextConnectUnix(c,path);
|
||||
return c;
|
||||
}
|
||||
|
||||
Vendored
+16
-3
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -34,7 +36,7 @@
|
||||
|
||||
#define HIREDIS_MAJOR 0
|
||||
#define HIREDIS_MINOR 9
|
||||
#define HIREDIS_PATCH 0
|
||||
#define HIREDIS_PATCH 2
|
||||
|
||||
#define REDIS_ERR -1
|
||||
#define REDIS_OK 0
|
||||
@@ -62,12 +64,16 @@
|
||||
* should be terminated once all replies have been read. */
|
||||
#define REDIS_DISCONNECTING 0x4
|
||||
|
||||
#define REDIS_REPLY_ERROR 0
|
||||
#define REDIS_REPLY_STRING 1
|
||||
#define REDIS_REPLY_ARRAY 2
|
||||
#define REDIS_REPLY_INTEGER 3
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is the reply object returned by redisCommand() */
|
||||
typedef struct redisReply {
|
||||
@@ -82,8 +88,10 @@ typedef struct redisReply {
|
||||
typedef struct redisReadTask {
|
||||
int type;
|
||||
int elements; /* number of elements in multibulk container */
|
||||
void *parent; /* optional pointer to parent object */
|
||||
int idx; /* index in parent (array) object */
|
||||
void *obj; /* holds user-generated value for a read task */
|
||||
struct redisReadTask *parent; /* parent task */
|
||||
void *privdata; /* user-settable arbitrary field */
|
||||
} redisReadTask;
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
@@ -112,6 +120,7 @@ typedef struct redisContext {
|
||||
void freeReplyObject(void *reply);
|
||||
void *redisReplyReaderCreate();
|
||||
int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn);
|
||||
int redisReplyReaderSetPrivdata(void *reader, void *privdata);
|
||||
void *redisReplyReaderGetObject(void *reader);
|
||||
char *redisReplyReaderGetError(void *reader);
|
||||
void redisReplyReaderFree(void *ptr);
|
||||
@@ -154,4 +163,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap);
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+6
-3
@@ -1,8 +1,9 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
@@ -43,7 +44,7 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
|
||||
/* Forward declaration */
|
||||
@@ -114,7 +115,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
|
||||
he = gethostbyname(addr);
|
||||
if (he == NULL) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,
|
||||
sdscatprintf(sdsempty(),"can't resolve: %s",addr));
|
||||
sdscatprintf(sdsempty(),"Can't resolve: %s",addr));
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -137,6 +138,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
|
||||
}
|
||||
|
||||
c->fd = s;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -163,5 +165,6 @@ int redisContextConnectUnix(redisContext *c, const char *path) {
|
||||
}
|
||||
|
||||
c->fd = s;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -31,6 +31,12 @@
|
||||
#ifndef __NET_H
|
||||
#define __NET_H
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port);
|
||||
int redisContextConnectUnix(redisContext *c, const char *path);
|
||||
|
||||
|
||||
Vendored
+110
-32
@@ -47,17 +47,59 @@ static void test_format_commands() {
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%s and an empty string: ");
|
||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%b string interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%b and an empty string: ");
|
||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with literal %%: ");
|
||||
len = redisFormatCommand(&cmd,"SET %% %%");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(1+2)+4+(1+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with printf-delegation (long long): ");
|
||||
len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
|
||||
test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
|
||||
len == 4+5+(12+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with printf-delegation (float): ");
|
||||
len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
|
||||
test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
|
||||
len == 4+4+(8+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with printf-delegation and extra interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
|
||||
test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
|
||||
len == 4+4+(8+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with wrong printf format and extra interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
|
||||
test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
|
||||
len == 4+4+(6+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
const char *argv[3];
|
||||
argv[0] = "SET";
|
||||
argv[1] = "foo";
|
||||
argv[1] = "foo\0xxx";
|
||||
argv[2] = "bar";
|
||||
size_t lens[3] = { 3, 3, 3 };
|
||||
size_t lens[3] = { 3, 7, 3 };
|
||||
int argc = 3;
|
||||
|
||||
test("Format command by passing argc/argv without lengths: ");
|
||||
@@ -68,38 +110,29 @@ static void test_format_commands() {
|
||||
|
||||
test("Format command by passing argc/argv with lengths: ");
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,lens);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
static void test_blocking_connection() {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
int major, minor;
|
||||
|
||||
__connect(&c);
|
||||
test("Returns I/O error when the connection is lost: ");
|
||||
reply = redisCommand(c,"QUIT");
|
||||
test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL);
|
||||
|
||||
/* Two conditions may happen, depending on the type of connection.
|
||||
* When connected via TCP, the socket will not yet be aware of the closed
|
||||
* connection and the write(2) call will succeed, but the read(2) will
|
||||
* result in an EOF. When connected via Unix sockets, the socket will be
|
||||
* immediately aware that it was closed and fail on the write(2) call. */
|
||||
if (use_unix) {
|
||||
fprintf(stderr,"Error: %s\n", c->errstr);
|
||||
assert(c->err == REDIS_ERR_IO &&
|
||||
strcmp(c->errstr,"Broken pipe") == 0);
|
||||
} else {
|
||||
fprintf(stderr,"Error: %s\n", c->errstr);
|
||||
assert(c->err == REDIS_ERR_EOF &&
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
c = redisConnect((char*)"idontexist.local", 6379);
|
||||
test_cond(c->err == REDIS_ERR_OTHER &&
|
||||
strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
|
||||
redisFree(c);
|
||||
|
||||
__connect(&c); /* reconnect */
|
||||
test("Returns error when the port is not open: ");
|
||||
c = redisConnect((char*)"localhost", 56380);
|
||||
test_cond(c->err == REDIS_ERR_IO &&
|
||||
strcmp(c->errstr,"Connection refused") == 0);
|
||||
redisFree(c);
|
||||
|
||||
__connect(&c);
|
||||
test("Is able to deliver commands: ");
|
||||
reply = redisCommand(c,"PING");
|
||||
test_cond(reply->type == REDIS_REPLY_STATUS &&
|
||||
@@ -112,12 +145,9 @@ static void test_blocking_connection() {
|
||||
|
||||
/* Make sure the DB is emtpy */
|
||||
reply = redisCommand(c,"DBSIZE");
|
||||
if (reply->type != REDIS_REPLY_INTEGER ||
|
||||
reply->integer != 0) {
|
||||
printf("Sorry DB 9 is not empty, test can not continue\n");
|
||||
if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
|
||||
printf("Database #9 is not empty, test can not continue\n");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("DB 9 is empty... test can continue\n");
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
@@ -183,6 +213,43 @@ static void test_blocking_connection() {
|
||||
reply->element[1]->type == REDIS_REPLY_STATUS &&
|
||||
strcasecmp(reply->element[1]->str,"pong") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
{
|
||||
/* Find out Redis version to determine the path for the next test */
|
||||
const char *field = "redis_version:";
|
||||
char *p, *eptr;
|
||||
|
||||
reply = redisCommand(c,"INFO");
|
||||
p = strstr(reply->str,field);
|
||||
major = strtol(p+strlen(field),&eptr,10);
|
||||
p = eptr+1; /* char next to the first "." */
|
||||
minor = strtol(p,&eptr,10);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
test("Returns I/O error when the connection is lost: ");
|
||||
reply = redisCommand(c,"QUIT");
|
||||
if (major >= 2 && minor > 0) {
|
||||
/* > 2.0 returns OK on QUIT and read() should be issued once more
|
||||
* to know the descriptor is at EOF. */
|
||||
test_cond(strcasecmp(reply->str,"OK") == 0 &&
|
||||
redisGetReply(c,(void**)&reply) == REDIS_ERR);
|
||||
freeReplyObject(reply);
|
||||
} else {
|
||||
test_cond(reply == NULL);
|
||||
}
|
||||
|
||||
/* On 2.0, QUIT will cause the connection to be closed immediately and
|
||||
* the read(2) for the reply on QUIT will set the error to EOF.
|
||||
* On >2.0, QUIT will return with OK and another read(2) needed to be
|
||||
* issued to find out the socket was closed by the server. In both
|
||||
* conditions, the error will be set to EOF. */
|
||||
assert(c->err == REDIS_ERR_EOF &&
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
|
||||
/* Clean up context and reconnect again */
|
||||
redisFree(c);
|
||||
__connect(&c);
|
||||
}
|
||||
|
||||
static void test_reply_reader() {
|
||||
@@ -197,7 +264,7 @@ static void test_reply_reader() {
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
/* when the reply already contains multiple items, they must be free'd
|
||||
@@ -210,7 +277,18 @@ static void test_reply_reader() {
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
|
||||
strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Set error on nested multi bulks with depth > 1: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
|
||||
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
|
||||
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strncasecmp(err,"No support for",14) == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Works with NULL functions for reply: ");
|
||||
|
||||
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];
|
||||
|
||||
+3
-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
|
||||
@@ -365,17 +366,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
|
||||
|
||||
+2
-1
@@ -8,9 +8,11 @@ OPTIMIZATION?=-O2
|
||||
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)
|
||||
@@ -18,7 +20,6 @@ ifeq ($(USE_TCMALLOC),yes)
|
||||
CFLAGS+= -DUSE_TCMALLOC
|
||||
endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -rdynamic -ggdb
|
||||
|
||||
PREFIX= /usr/local
|
||||
INSTALL_BIN= $(PREFIX)/bin
|
||||
|
||||
+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);
|
||||
|
||||
+59
-5
@@ -66,7 +66,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 +194,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;
|
||||
@@ -428,6 +426,26 @@ 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 {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -450,6 +468,17 @@ void configGetCommand(redisClient *c) {
|
||||
int matches = 0;
|
||||
redisAssert(o->encoding == REDIS_ENCODING_RAW);
|
||||
|
||||
if (stringmatch(pattern,"dir",0)) {
|
||||
char buf[1024];
|
||||
|
||||
addReplyBulkCString(c,"dir");
|
||||
if (getcwd(buf,sizeof(buf)) == NULL) {
|
||||
buf[0] = '\0';
|
||||
} else {
|
||||
addReplyBulkCString(c,buf);
|
||||
}
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"dbfilename",0)) {
|
||||
addReplyBulkCString(c,"dbfilename");
|
||||
addReplyBulkCString(c,server.dbfilename);
|
||||
@@ -543,6 +572,31 @@ 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++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,matches*2);
|
||||
}
|
||||
|
||||
|
||||
@@ -453,6 +453,8 @@ 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 */
|
||||
|
||||
/* 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 +466,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;
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@ void execCommand(redisClient *c) {
|
||||
c->argc = c->mstate.commands[j].argc;
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
call(c,c->mstate.commands[j].cmd);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
c->mstate.commands[j].argv = c->argv;
|
||||
}
|
||||
c->argv = orig_argv;
|
||||
c->argc = orig_argc;
|
||||
|
||||
+26
-88
@@ -457,6 +457,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,7 +498,6 @@ 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
|
||||
@@ -516,15 +522,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) {
|
||||
@@ -595,84 +592,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 +827,22 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
+110
-144
@@ -45,10 +45,6 @@
|
||||
#include "adlist.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
#define CLIENT_CONNECTING 0
|
||||
#define CLIENT_SENDQUERY 1
|
||||
#define CLIENT_READREPLY 2
|
||||
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
|
||||
static struct config {
|
||||
@@ -78,10 +74,10 @@ static struct config {
|
||||
|
||||
typedef struct _client {
|
||||
redisContext *context;
|
||||
int state;
|
||||
sds obuf;
|
||||
char *randptr[10]; /* needed for MSET against 10 keys */
|
||||
size_t randlen;
|
||||
unsigned int written; /* bytes of 'obuf' already written */
|
||||
int replytype;
|
||||
long long start; /* start time of a request */
|
||||
long long latency; /* request latency */
|
||||
} *client;
|
||||
@@ -139,22 +135,17 @@ static void resetClient(client c) {
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
c->written = 0;
|
||||
c->state = CLIENT_SENDQUERY;
|
||||
c->start = ustime();
|
||||
c->latency = -1;
|
||||
}
|
||||
|
||||
static void randomizeClientKey(client c) {
|
||||
char *p;
|
||||
char buf[32];
|
||||
long r;
|
||||
size_t i, r;
|
||||
|
||||
p = strstr(c->obuf, "_rand");
|
||||
if (!p) return;
|
||||
p += 5;
|
||||
r = random() % config.randomkeys_keyspacelen;
|
||||
sprintf(buf,"%ld",r);
|
||||
memcpy(p,buf,strlen(buf));
|
||||
for (i = 0; i < c->randlen; i++) {
|
||||
r = random() % config.randomkeys_keyspacelen;
|
||||
snprintf(buf,sizeof(buf),"%012zu",r);
|
||||
memcpy(c->randptr[i],buf,12);
|
||||
}
|
||||
}
|
||||
|
||||
static void clientDone(client c) {
|
||||
@@ -165,7 +156,6 @@ static void clientDone(client c) {
|
||||
}
|
||||
if (config.keepalive) {
|
||||
resetClient(c);
|
||||
if (config.randomkeys) randomizeClientKey(c);
|
||||
} else {
|
||||
config.liveclients--;
|
||||
createMissingClients(c);
|
||||
@@ -195,6 +185,11 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
exit(1);
|
||||
}
|
||||
if (reply != NULL) {
|
||||
if (reply == (void*)REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr,"Unexpected error reply, exiting...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (config.donerequests < config.requests)
|
||||
config.latency[config.donerequests++] = c->latency;
|
||||
clientDone(c);
|
||||
@@ -208,11 +203,13 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
REDIS_NOTUSED(fd);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
if (c->state == CLIENT_CONNECTING) {
|
||||
c->state = CLIENT_SENDQUERY;
|
||||
/* When nothing was written yet, randomize keys and set start time. */
|
||||
if (c->written == 0) {
|
||||
if (config.randomkeys) randomizeClientKey(c);
|
||||
c->start = ustime();
|
||||
c->latency = -1;
|
||||
}
|
||||
|
||||
if (sdslen(c->obuf) > c->written) {
|
||||
void *ptr = c->obuf+c->written;
|
||||
int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
|
||||
@@ -226,12 +223,11 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (sdslen(c->obuf) == c->written) {
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c);
|
||||
c->state = CLIENT_READREPLY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static client createClient(int replytype) {
|
||||
static client createClient(char *cmd, int len) {
|
||||
client c = zmalloc(sizeof(struct _client));
|
||||
if (config.hostsocket == NULL) {
|
||||
c->context = redisConnectNonBlock(config.hostip,config.hostport);
|
||||
@@ -246,10 +242,22 @@ static client createClient(int replytype) {
|
||||
fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
|
||||
exit(1);
|
||||
}
|
||||
c->replytype = replytype;
|
||||
c->state = CLIENT_CONNECTING;
|
||||
c->obuf = sdsempty();
|
||||
c->obuf = sdsnewlen(cmd,len);
|
||||
c->randlen = 0;
|
||||
c->written = 0;
|
||||
|
||||
/* Find substrings in the output buffer that need to be randomized. */
|
||||
if (config.randomkeys) {
|
||||
char *p = c->obuf, *newline;
|
||||
while ((p = strstr(p,":rand:")) != NULL) {
|
||||
newline = strstr(p,"\r\n");
|
||||
assert(newline-(p+6) == 12); /* 12 chars for randomness */
|
||||
assert(c->randlen < (signed)(sizeof(c->randptr)/sizeof(char*)));
|
||||
c->randptr[c->randlen++] = p+6;
|
||||
p = newline+2;
|
||||
}
|
||||
}
|
||||
|
||||
redisSetReplyObjectFunctions(c->context,NULL);
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
listAddNodeTail(config.clients,c);
|
||||
@@ -258,11 +266,16 @@ static client createClient(int replytype) {
|
||||
}
|
||||
|
||||
static void createMissingClients(client c) {
|
||||
int n = 0;
|
||||
|
||||
while(config.liveclients < config.numclients) {
|
||||
client new = createClient(c->replytype);
|
||||
sdsfree(new->obuf);
|
||||
new->obuf = sdsdup(c->obuf);
|
||||
if (config.randomkeys) randomizeClientKey(c);
|
||||
createClient(c->obuf,sdslen(c->obuf));
|
||||
|
||||
/* Listen backlog is quite limited on most systems */
|
||||
if (++n > 64) {
|
||||
usleep(50000);
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,14 +311,19 @@ static void showLatencyReport(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void prepareForBenchmark(char *title) {
|
||||
config.title = title;
|
||||
config.start = mstime();
|
||||
config.donerequests = 0;
|
||||
}
|
||||
static void benchmark(char *title, char *cmd, int len) {
|
||||
client c;
|
||||
|
||||
static void endBenchmark(void) {
|
||||
config.title = title;
|
||||
config.donerequests = 0;
|
||||
|
||||
c = createClient(cmd,len);
|
||||
createMissingClients(c);
|
||||
|
||||
config.start = mstime();
|
||||
aeMain(config.el);
|
||||
config.totlatency = mstime()-config.start;
|
||||
|
||||
showLatencyReport();
|
||||
freeAllClients();
|
||||
}
|
||||
@@ -392,6 +410,7 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int i;
|
||||
client c;
|
||||
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
@@ -426,136 +445,83 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (config.idlemode) {
|
||||
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
|
||||
prepareForBenchmark("IDLE");
|
||||
c = createClient(0); /* will never receive a reply */
|
||||
c->obuf = sdsempty();
|
||||
c = createClient("",0); /* will never receive a reply */
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
/* and will wait for every */
|
||||
}
|
||||
|
||||
do {
|
||||
prepareForBenchmark("PING");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"PING\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
char *data, *cmd;
|
||||
int len;
|
||||
|
||||
prepareForBenchmark("PING (multi bulk)");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
data = zmalloc(config.datasize+1);
|
||||
memset(data,'x',config.datasize);
|
||||
data[config.datasize] = '\0';
|
||||
|
||||
prepareForBenchmark("MSET (10 keys, multi bulk)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscatprintf(c->obuf,"*%d\r\n$4\r\nMSET\r\n", 11);
|
||||
{
|
||||
int i;
|
||||
char *data = zmalloc(config.datasize+2);
|
||||
memset(data,'x',config.datasize);
|
||||
for (i = 0; i < 10; i++) {
|
||||
c->obuf = sdscatprintf(c->obuf,"$%d\r\n%s\r\n",config.datasize,data);
|
||||
}
|
||||
zfree(data);
|
||||
benchmark("PING (inline)","PING\r\n",6);
|
||||
|
||||
len = redisFormatCommand(&cmd,"PING");
|
||||
benchmark("PING",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
const char *argv[21];
|
||||
argv[0] = "MSET";
|
||||
for (i = 1; i < 21; i += 2) {
|
||||
argv[i] = "foo:rand:000000000000";
|
||||
argv[i+1] = data;
|
||||
}
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommandArgv(&cmd,21,argv,NULL);
|
||||
benchmark("MSET (10 keys)",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("SET");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"*3\r\n$3\r\nSET\r\n$20\r\nfoo_rand000000000000\r\n");
|
||||
{
|
||||
char *data = zmalloc(config.datasize+2);
|
||||
memset(data,'x',config.datasize);
|
||||
data[config.datasize] = '\r';
|
||||
data[config.datasize+1] = '\n';
|
||||
c->obuf = sdscatprintf(c->obuf,"$%d\r\n",config.datasize);
|
||||
c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
|
||||
}
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data);
|
||||
benchmark("SET",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("GET");
|
||||
c = createClient(REDIS_REPLY_STRING);
|
||||
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"GET foo:rand:000000000000");
|
||||
benchmark("GET",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("INCR");
|
||||
c = createClient(REDIS_REPLY_INTEGER);
|
||||
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000");
|
||||
benchmark("INCR",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("LPUSH");
|
||||
c = createClient(REDIS_REPLY_INTEGER);
|
||||
c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
|
||||
benchmark("LPUSH",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("LPOP");
|
||||
c = createClient(REDIS_REPLY_STRING);
|
||||
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"LPOP mylist");
|
||||
benchmark("LPOP",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("SADD");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"SADD myset counter_rand000000000000\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"SADD myset counter:rand:000000000000");
|
||||
benchmark("SADD",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("SPOP");
|
||||
c = createClient(REDIS_REPLY_STRING);
|
||||
c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"SPOP myset");
|
||||
benchmark("SPOP",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
|
||||
c = createClient(REDIS_REPLY_STATUS);
|
||||
c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
|
||||
benchmark("LPUSH (again, in order to bench LRANGE)",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("LRANGE (first 100 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist 0 99");
|
||||
benchmark("LRANGE (first 100 elements)",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("LRANGE (first 300 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist 0 299");
|
||||
benchmark("LRANGE (first 300 elements)",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("LRANGE (first 450 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist 0 449");
|
||||
benchmark("LRANGE (first 450 elements)",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
prepareForBenchmark("LRANGE (first 600 elements)");
|
||||
c = createClient(REDIS_REPLY_ARRAY);
|
||||
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
endBenchmark();
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist 0 599");
|
||||
benchmark("LRANGE (first 600 elements)",cmd,len);
|
||||
free(cmd);
|
||||
|
||||
printf("\n");
|
||||
} while(config.loop);
|
||||
|
||||
+3
-1
@@ -414,10 +414,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) {
|
||||
@@ -431,6 +432,7 @@ static int cliReadReply(int output_raw_strings) {
|
||||
return REDIS_ERR; /* avoid compiler warning */
|
||||
}
|
||||
|
||||
reply = (redisReply*)_reply;
|
||||
if (output_raw_strings) {
|
||||
out = cliFormatReplyRaw(reply);
|
||||
} else {
|
||||
|
||||
+31
-7
@@ -516,7 +516,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 +645,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* to detect transfer failures. */
|
||||
if (!(loops % 10)) replicationCron();
|
||||
|
||||
server.cronloops++;
|
||||
return 100;
|
||||
}
|
||||
|
||||
@@ -687,6 +688,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 +770,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;
|
||||
@@ -851,10 +852,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 */
|
||||
@@ -912,6 +916,7 @@ void initServer() {
|
||||
}
|
||||
|
||||
if (server.vm_enabled) vmInit();
|
||||
srand(time(NULL)^getpid());
|
||||
}
|
||||
|
||||
/* Populates the Redis Command Table starting from the hard coded list
|
||||
@@ -1142,9 +1147,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(),
|
||||
@@ -1163,6 +1170,8 @@ sds genRedisInfoString(void) {
|
||||
"used_cpu_user_childrens:%.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"
|
||||
@@ -1202,6 +1211,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,
|
||||
@@ -1314,6 +1324,19 @@ sds genRedisInfoString(void) {
|
||||
eta
|
||||
);
|
||||
}
|
||||
|
||||
info = sdscat(info,"allocation_stats:");
|
||||
for (j = 0; j <= ZMALLOC_MAX_ALLOC_STAT; j++) {
|
||||
size_t count = zmalloc_allocations_for_size(j);
|
||||
if (count) {
|
||||
if (info[sdslen(info)-1] != ':') info = sdscatlen(info,",",1);
|
||||
info = sdscatprintf(info,"%s%d=%zu",
|
||||
(j == ZMALLOC_MAX_ALLOC_STAT) ? ">=" : "",
|
||||
j,count);
|
||||
}
|
||||
}
|
||||
info = sdscat(info,"\r\n");
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
long long keys, vkeys;
|
||||
|
||||
@@ -1438,6 +1461,7 @@ void freeMemoryIfNeeded(void) {
|
||||
/* Finally remove the selected key. */
|
||||
if (bestkey) {
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
propagateExpire(db,keyobj);
|
||||
dbDelete(db,keyobj);
|
||||
server.stat_evictedkeys++;
|
||||
decrRefCount(keyobj);
|
||||
@@ -1476,7 +1500,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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-9
@@ -50,11 +50,6 @@
|
||||
#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
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
|
||||
@@ -146,6 +141,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 +191,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
|
||||
@@ -392,7 +389,6 @@ struct redisServer {
|
||||
long long stat_keyspace_misses; /* number of failed lookups of keys */
|
||||
/* Configuration */
|
||||
int verbosity;
|
||||
int glueoutputbuf;
|
||||
int maxidletime;
|
||||
int dbnum;
|
||||
int daemonize;
|
||||
@@ -641,7 +637,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);
|
||||
@@ -663,6 +658,8 @@ 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);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
|
||||
+48
-2
@@ -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();
|
||||
@@ -488,17 +494,26 @@ 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...");
|
||||
@@ -507,4 +522,35 @@ void replicationCron(void) {
|
||||
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. */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -773,7 +773,8 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
zfree(c->bpop.keys);
|
||||
c->bpop.keys = NULL;
|
||||
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);
|
||||
}
|
||||
@@ -825,7 +826,7 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
|
||||
addReplyBulk(receiver,ele);
|
||||
return 1;
|
||||
} 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);
|
||||
@@ -940,7 +941,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]);
|
||||
|
||||
+15
-2
@@ -334,12 +334,25 @@ 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);
|
||||
}
|
||||
|
||||
/* Change argv to replicate as SREM */
|
||||
c->argc = 3;
|
||||
c->argv = zrealloc(c->argv,sizeof(robj*)*(c->argc));
|
||||
|
||||
/* Overwrite SREM with SPOP (same length) */
|
||||
redisAssert(sdslen(c->argv[0]->ptr) == 4);
|
||||
memcpy(c->argv[0]->ptr, "SREM", 4);
|
||||
|
||||
/* Popped element already has incremented refcount */
|
||||
c->argv[2] = ele;
|
||||
|
||||
addReplyBulk(c,ele);
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <limits.h>
|
||||
#include "redis.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
|
||||
+5
-5
@@ -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;
|
||||
@@ -810,10 +810,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 +1039,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);
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.1.10"
|
||||
#define REDIS_VERSION "2.2.1"
|
||||
|
||||
+4
-2
@@ -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;
|
||||
|
||||
+24
-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,19 +53,22 @@
|
||||
#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); \
|
||||
size_t _stat_slot = (__size < ZMALLOC_MAX_ALLOC_STAT) ? __size : ZMALLOC_MAX_ALLOC_STAT; \
|
||||
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
|
||||
if (zmalloc_thread_safe) { \
|
||||
pthread_mutex_lock(&used_memory_mutex); \
|
||||
used_memory += _n; \
|
||||
zmalloc_allocations[_stat_slot]++; \
|
||||
pthread_mutex_unlock(&used_memory_mutex); \
|
||||
} else { \
|
||||
used_memory += _n; \
|
||||
zmalloc_allocations[_stat_slot]++; \
|
||||
} \
|
||||
} 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) { \
|
||||
@@ -79,6 +83,8 @@
|
||||
static size_t used_memory = 0;
|
||||
static int zmalloc_thread_safe = 0;
|
||||
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
/* Note that malloc_allocations elements are initialized to zero by C */
|
||||
size_t zmalloc_allocations[ZMALLOC_MAX_ALLOC_STAT+1];
|
||||
|
||||
static void zmalloc_oom(size_t size) {
|
||||
fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
|
||||
@@ -92,11 +98,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 +112,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 +134,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 +144,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 +158,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
|
||||
}
|
||||
@@ -179,6 +185,11 @@ size_t zmalloc_used_memory(void) {
|
||||
return um;
|
||||
}
|
||||
|
||||
size_t zmalloc_allocations_for_size(size_t size) {
|
||||
if (size > ZMALLOC_MAX_ALLOC_STAT) return 0;
|
||||
return zmalloc_allocations[size];
|
||||
}
|
||||
|
||||
void zmalloc_enable_thread_safeness(void) {
|
||||
zmalloc_thread_safe = 1;
|
||||
}
|
||||
@@ -251,7 +262,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()..
|
||||
*
|
||||
|
||||
@@ -40,5 +40,8 @@ size_t zmalloc_used_memory(void);
|
||||
void zmalloc_enable_thread_safeness(void);
|
||||
float zmalloc_get_fragmentation_ratio(void);
|
||||
size_t zmalloc_get_rss(void);
|
||||
size_t zmalloc_allocations_for_size(size_t size);
|
||||
|
||||
#define ZMALLOC_MAX_ALLOC_STAT 256
|
||||
|
||||
#endif /* _ZMALLOC_H */
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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"
|
||||
Reference in New Issue
Block a user