Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
0540df22ba | ||
|
|
9ac5be2ebe | ||
|
|
b902c1413e | ||
|
|
54e9f28922 | ||
|
|
dd889d884f | ||
|
|
1443a814d0 | ||
|
|
f474a5bd4e | ||
|
|
1cd3c1e08c | ||
|
|
174df6fe49 | ||
|
|
53f1d81712 | ||
|
|
021321e0ef | ||
|
|
076ff11c06 | ||
|
|
4769dc7826 | ||
|
|
039357e471 |
+27
-3
@@ -1,4 +1,26 @@
|
||||
Welcome to Redis 2.1.9 (2.2 Release Candidate 1)
|
||||
Redis 2.2 release notes
|
||||
|
||||
Migrating from 2.0 to 2.2
|
||||
=========================
|
||||
|
||||
Redis 2.0 is mostly a strict subset of 2.2. Some return value changed in edge
|
||||
cases, basicaly it is very unlikely that you will experience any problem
|
||||
upgrading your 2.0 instances to 2.2, as 2.2 can work as a drop in replacement
|
||||
for 2.0.
|
||||
|
||||
What's new in Redis 2.1.10 (2.2 Release Candidate 2)
|
||||
====================================================
|
||||
|
||||
Redis 2.2 RC2 is exactly like RC1 with the following minor changes:
|
||||
|
||||
* Added evicted keys counter separated from expired keys.
|
||||
* Overflow detection in INCR family functions.
|
||||
|
||||
Enjoy,
|
||||
Salvatore
|
||||
|
||||
What's new in Redis 2.1.9 (2.2 Release Candidate 1)
|
||||
===================================================
|
||||
|
||||
This is the first Release Candidate of Redis 2.2, in our experience the
|
||||
server is very stable, but in the latest weeks we rewrote part of the internals
|
||||
@@ -7,7 +29,8 @@ a BGREWRITEAOF or a BGSAVE, so handle with care for a couple of weeks.
|
||||
|
||||
Oh, and I've some very good news: the majority of apps can work if you simply replace 2.2 in your old 2.0 environment. I can't think of any breakage.
|
||||
|
||||
WHAT'S NEW IN REDIS 2.2.x
|
||||
WHAT'S NEW IN REDIS 2.2 compared to the 2.0 version?
|
||||
====================================================
|
||||
|
||||
* Specially encoded data types, small lists and sets can now use up to an order of magnitude less memory.
|
||||
* VM partial rewrite for code cleaness and memory usage.
|
||||
@@ -26,10 +49,11 @@ WHAT'S NEW IN REDIS 2.2.x
|
||||
* BRPOPLPUSH (Thanks to Michel Martens and Damian Janowski)
|
||||
* Much more interesting informations in the INFO output.
|
||||
* Sorted sets are now less memory hungry.
|
||||
* Non blocking loading of .rdb / AOF file on startup, with process information in the INFO output.
|
||||
* Non blocking loading of .rdb / AOF file on startup, with progress information in the INFO output.
|
||||
* Now Redis has a clean, powerful, supported C library: hiredis.
|
||||
* Code layout completely new, the 2.0.x huge redis.c file is now splitted in many parts.
|
||||
* Redis-benchmark rewritten to be faster and in order to use hiredis as well.
|
||||
* Ability to rename or disable commands from the config file.
|
||||
* Endless other CPU optimizations and bugs fixed.
|
||||
|
||||
Credits: Where not specified the implementation and design are done by Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all this possible. Also many thanks to all the other contributors and the amazing community we have.
|
||||
|
||||
@@ -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
|
||||
|
||||
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: ");
|
||||
|
||||
+2
-7
@@ -365,17 +365,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);
|
||||
|
||||
+2
-4
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+26
-87
@@ -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) {
|
||||
@@ -516,15 +523,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 +593,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 +828,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),"%012lu",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 {
|
||||
|
||||
+24
-34
@@ -687,6 +687,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 +769,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;
|
||||
@@ -891,6 +891,7 @@ void initServer() {
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_starttime = time(NULL);
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
@@ -1141,9 +1142,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(),
|
||||
@@ -1162,6 +1165,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"
|
||||
@@ -1177,6 +1182,7 @@ sds genRedisInfoString(void) {
|
||||
"total_connections_received:%lld\r\n"
|
||||
"total_commands_processed:%lld\r\n"
|
||||
"expired_keys:%lld\r\n"
|
||||
"evicted_keys:%lld\r\n"
|
||||
"keyspace_hits:%lld\r\n"
|
||||
"keyspace_misses:%lld\r\n"
|
||||
"hash_max_zipmap_entries:%zu\r\n"
|
||||
@@ -1200,6 +1206,7 @@ sds genRedisInfoString(void) {
|
||||
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
listLength(server.slaves),
|
||||
lol, bib,
|
||||
server.bpop_blocked_clients,
|
||||
zmalloc_used_memory(),
|
||||
hmem,
|
||||
@@ -1219,6 +1226,7 @@ sds genRedisInfoString(void) {
|
||||
server.stat_numconnections,
|
||||
server.stat_numcommands,
|
||||
server.stat_expiredkeys,
|
||||
server.stat_evictedkeys,
|
||||
server.stat_keyspace_hits,
|
||||
server.stat_keyspace_misses,
|
||||
server.hash_max_zipmap_entries,
|
||||
@@ -1311,6 +1319,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;
|
||||
|
||||
@@ -1436,44 +1457,13 @@ void freeMemoryIfNeeded(void) {
|
||||
if (bestkey) {
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
dbDelete(db,keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
server.stat_evictedkeys++;
|
||||
decrRefCount(keyobj);
|
||||
freed++;
|
||||
}
|
||||
}
|
||||
if (!freed) return; /* nothing to free... */
|
||||
}
|
||||
|
||||
while(0) {
|
||||
int j, k, freed = 0;
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
int minttl = -1;
|
||||
sds minkey = NULL;
|
||||
robj *keyobj = NULL;
|
||||
struct dictEntry *de;
|
||||
|
||||
if (dictSize(server.db[j].expires)) {
|
||||
freed = 1;
|
||||
/* From a sample of three keys drop the one nearest to
|
||||
* the natural expire */
|
||||
for (k = 0; k < 3; k++) {
|
||||
time_t t;
|
||||
|
||||
de = dictGetRandomKey(server.db[j].expires);
|
||||
t = (time_t) dictGetEntryVal(de);
|
||||
if (minttl == -1 || t < minttl) {
|
||||
minkey = dictGetEntryKey(de);
|
||||
minttl = t;
|
||||
}
|
||||
}
|
||||
keyobj = createStringObject(minkey,sdslen(minkey));
|
||||
dbDelete(server.db+j,keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
}
|
||||
if (!freed) return; /* nothing to free... */
|
||||
}
|
||||
}
|
||||
|
||||
/* =================================== Main! ================================ */
|
||||
@@ -1504,7 +1494,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);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-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
|
||||
@@ -387,11 +384,11 @@ struct redisServer {
|
||||
long long stat_numcommands; /* number of processed commands */
|
||||
long long stat_numconnections; /* number of connections received */
|
||||
long long stat_expiredkeys; /* number of expired keys */
|
||||
long long stat_evictedkeys; /* number of evicted keys (maxmemory) */
|
||||
long long stat_keyspace_hits; /* number of successful lookups of keys */
|
||||
long long stat_keyspace_misses; /* number of failed lookups of keys */
|
||||
/* Configuration */
|
||||
int verbosity;
|
||||
int glueoutputbuf;
|
||||
int maxidletime;
|
||||
int dbnum;
|
||||
int daemonize;
|
||||
@@ -640,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);
|
||||
@@ -662,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, ...)
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
+6
-2
@@ -1,4 +1,3 @@
|
||||
#include <limits.h>
|
||||
#include "redis.h"
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -346,14 +345,19 @@ void msetnxCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void incrDecrCommand(redisClient *c, long long incr) {
|
||||
long long value;
|
||||
long long value, oldvalue;
|
||||
robj *o;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o != NULL && checkType(c,o,REDIS_STRING)) return;
|
||||
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
|
||||
|
||||
oldvalue = value;
|
||||
value += incr;
|
||||
if ((incr < 0 && value > oldvalue) || (incr > 0 && value < oldvalue)) {
|
||||
addReplyError(c,"increment or decrement would overflow");
|
||||
return;
|
||||
}
|
||||
o = createStringObjectFromLongLong(value);
|
||||
dbReplace(c->db,c->argv[1],o);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
|
||||
+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.8"
|
||||
#define REDIS_VERSION "2.1.11"
|
||||
|
||||
+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;
|
||||
|
||||
+23
-12
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user