Browse Source
Add detecting of libhiredis for communicating with kvstorage.
Add detecting of libhiredis for communicating with kvstorage.
Add internal hiredis if it is not found in system.rspamd-0.5
25 changed files with 5165 additions and 0 deletions
-
19CMakeLists.txt
-
6contrib/hiredis/.gitignore
-
16contrib/hiredis/CHANGELOG.md
-
14contrib/hiredis/CMakeLists.txt
-
29contrib/hiredis/COPYING
-
351contrib/hiredis/README.md
-
95contrib/hiredis/adapters/ae.h
-
113contrib/hiredis/adapters/libev.h
-
75contrib/hiredis/adapters/libevent.h
-
552contrib/hiredis/async.c
-
125contrib/hiredis/async.h
-
338contrib/hiredis/dict.c
-
126contrib/hiredis/dict.h
-
53contrib/hiredis/example-ae.c
-
47contrib/hiredis/example-libev.c
-
48contrib/hiredis/example-libevent.c
-
68contrib/hiredis/example.c
-
16contrib/hiredis/fmacros.h
-
1237contrib/hiredis/hiredis.c
-
204contrib/hiredis/hiredis.h
-
256contrib/hiredis/net.c
-
46contrib/hiredis/net.h
-
605contrib/hiredis/sds.c
-
88contrib/hiredis/sds.h
-
638contrib/hiredis/test.c
@ -0,0 +1,6 @@ |
|||
/hiredis-test |
|||
/hiredis-example* |
|||
/*.o |
|||
/*.so |
|||
/*.dylib |
|||
/*.a |
|||
@ -0,0 +1,16 @@ |
|||
### 0.10.1 |
|||
|
|||
* Makefile overhaul. Important to check out if you override one or more |
|||
variables using environment variables or via arguments to the "make" tool. |
|||
|
|||
* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements |
|||
being created by the default reply object functions. |
|||
|
|||
* Issue #43: Don't crash in an asynchronous context when Redis returns an error |
|||
reply after the connection has been made (this happens when the maximum |
|||
number of connections is reached). |
|||
|
|||
### 0.10.0 |
|||
|
|||
* See commit log. |
|||
|
|||
@ -0,0 +1,14 @@ |
|||
# Hiredis compilation target |
|||
|
|||
SET(LIBHIREDISSRC async.c |
|||
dict.c |
|||
hiredis.c |
|||
net.c |
|||
sds.c) |
|||
|
|||
ADD_LIBRARY(hiredis STATIC ${LIBHIREDISSRC}) |
|||
IF(CMAKE_COMPILER_IS_GNUCC) |
|||
SET_TARGET_PROPERTIES(hiredis PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing") |
|||
ENDIF(CMAKE_COMPILER_IS_GNUCC) |
|||
|
|||
TARGET_LINK_LIBRARIES(hiredis ${CMAKE_REQUIRED_LIBRARIES}) |
|||
@ -0,0 +1,29 @@ |
|||
Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
|||
Copyright (c) 2010-2011, 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: |
|||
|
|||
* Redistributions of source code must retain the above copyright notice, |
|||
this list of conditions and the following disclaimer. |
|||
|
|||
* Redistributions in binary form must reproduce the above copyright notice, |
|||
this list of conditions and the following disclaimer in the documentation |
|||
and/or other materials provided with the distribution. |
|||
|
|||
* Neither the name of Redis nor the names of its contributors may be used |
|||
to endorse or promote products derived from this software without specific |
|||
prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
|||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
|||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
@ -0,0 +1,351 @@ |
|||
# HIREDIS |
|||
|
|||
Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. |
|||
|
|||
It is minimalistic because it just adds minimal support for the protocol, but |
|||
at the same time it uses an high level printf-alike API in order to make it |
|||
much higher level than otherwise suggested by its minimal code base and the |
|||
lack of explicit bindings for every Redis command. |
|||
|
|||
Apart from supporting sending commands and receiving replies, it comes with |
|||
a reply parser that is decoupled from the I/O layer. It |
|||
is a stream parser designed for easy reusability, which can for instance be used |
|||
in higher level language bindings for efficient reply parsing. |
|||
|
|||
Hiredis only supports the binary-safe Redis protocol, so you can use it with any |
|||
Redis version >= 1.2.0. |
|||
|
|||
The library comes with multiple APIs. There is the |
|||
*synchronous API*, the *asynchronous API* and the *reply parsing API*. |
|||
|
|||
## UPGRADING |
|||
|
|||
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing |
|||
code using hiredis should not be a big pain. The key thing to keep in mind when |
|||
upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to |
|||
the stateless 0.0.1 that only has a file descriptor to work with. |
|||
|
|||
## Synchronous API |
|||
|
|||
To consume the synchronous API, there are only a few function calls that need to be introduced: |
|||
|
|||
redisContext *redisConnect(const char *ip, int port); |
|||
void *redisCommand(redisContext *c, const char *format, ...); |
|||
void freeReplyObject(void *reply); |
|||
|
|||
### 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 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->err) { |
|||
printf("Error: %s\n", c->errstr); |
|||
// handle error |
|||
} |
|||
|
|||
### Sending commands |
|||
|
|||
There are several ways to issue commands to Redis. The first that will be introduced is |
|||
`redisCommand`. This function takes a format similar to printf. In the simplest form, |
|||
it is used like this: |
|||
|
|||
reply = redisCommand(context, "SET foo bar"); |
|||
|
|||
The specifier `%s` interpolates a string in the command, and uses `strlen` to |
|||
determine the length of the string: |
|||
|
|||
reply = redisCommand(context, "SET foo %s", value); |
|||
|
|||
When you need to pass binary safe strings in a command, the `%b` specifier can be |
|||
used. Together with a pointer to the string, it requires a `size_t` length argument |
|||
of the string: |
|||
|
|||
reply = redisCommand(context, "SET foo %b", value, valuelen); |
|||
|
|||
Internally, Hiredis splits the command in different arguments and will |
|||
convert it to the protocol used to communicate with Redis. |
|||
One or more spaces separates arguments, so you can use the specifiers |
|||
anywhere in an argument: |
|||
|
|||
reply = redisCommand("SET key:%s %s", myid, value); |
|||
|
|||
### Using replies |
|||
|
|||
The return value of `redisCommand` holds a reply when the command was |
|||
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. |
|||
|
|||
The standard replies that `redisCommand` are of the type `redisReply`. The |
|||
`type` field in the `redisReply` should be used to test what kind of reply |
|||
was received: |
|||
|
|||
* **`REDIS_REPLY_STATUS`**: |
|||
* The command replied with a status reply. The status string can be accessed using `reply->str`. |
|||
The length of this string can be accessed using `reply->len`. |
|||
|
|||
* **`REDIS_REPLY_ERROR`**: |
|||
* The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. |
|||
|
|||
* **`REDIS_REPLY_INTEGER`**: |
|||
* The command replied with an integer. The integer value can be accessed using the |
|||
`reply->integer` field of type `long long`. |
|||
|
|||
* **`REDIS_REPLY_NIL`**: |
|||
* The command replied with a **nil** object. There is no data to access. |
|||
|
|||
* **`REDIS_REPLY_STRING`**: |
|||
* A bulk (string) reply. The value of the reply can be accessed using `reply->str`. |
|||
The length of this string can be accessed using `reply->len`. |
|||
|
|||
* **`REDIS_REPLY_ARRAY`**: |
|||
* A multi bulk reply. The number of elements in the multi bulk reply is stored in |
|||
`reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well |
|||
and can be accessed via `reply->element[..index..]`. |
|||
Redis may reply with nested arrays but this is fully supported. |
|||
|
|||
Replies should be freed using the `freeReplyObject()` function. |
|||
Note that this function will take care of freeing sub-replies objects |
|||
contained in arrays and nested arrays, so there is no need for the user to |
|||
free the sub replies (it is actually harmful and will corrupt the memory). |
|||
|
|||
**Important:** the current version of hiredis (0.10.0) free's replies when the |
|||
asynchronous API is used. This means you should not call `freeReplyObject` when |
|||
you use this API. The reply is cleaned up by hiredis _after_ the callback |
|||
returns. This behavior will probably change in future releases, so make sure to |
|||
keep an eye on the changelog when upgrading (see issue #39). |
|||
|
|||
### Cleaning up |
|||
|
|||
To disconnect and free the context the following function can be used: |
|||
|
|||
void redisFree(redisContext *c); |
|||
|
|||
This function immediately closes the socket and then free's the allocations done in |
|||
creating the context. |
|||
|
|||
### Sending commands (cont'd) |
|||
|
|||
Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. |
|||
It has the following prototype: |
|||
|
|||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); |
|||
|
|||
It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the |
|||
arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will |
|||
use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments |
|||
need to be binary safe, the entire array of lengths `argvlen` should be provided. |
|||
|
|||
The return value has the same semantic as `redisCommand`. |
|||
|
|||
### Pipelining |
|||
|
|||
To explain how Hiredis supports pipelining in a blocking connection, there needs to be |
|||
understanding of the internal execution flow. |
|||
|
|||
When any of the functions in the `redisCommand` family is called, Hiredis first formats the |
|||
command according to the Redis protocol. The formatted command is then put in the output buffer |
|||
of the context. This output buffer is dynamic, so it can hold any number of commands. |
|||
After the command is put in the output buffer, `redisGetReply` is called. This function has the |
|||
following two execution paths: |
|||
|
|||
1. The input buffer is non-empty: |
|||
* Try to parse a single reply from the input buffer and return it |
|||
* If no reply could be parsed, continue at *2* |
|||
2. The input buffer is empty: |
|||
* Write the **entire** output buffer to the socket |
|||
* Read from the socket until a single reply could be parsed |
|||
|
|||
The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply |
|||
is expected on the socket. To pipeline commands, the only things that needs to be done is |
|||
filling up the output buffer. For this cause, two commands can be used that are identical |
|||
to the `redisCommand` family, apart from not returning a reply: |
|||
|
|||
void redisAppendCommand(redisContext *c, const char *format, ...); |
|||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); |
|||
|
|||
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 `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 `read(2)`): |
|||
|
|||
redisReply *reply; |
|||
redisAppendCommand(context,"SET foo bar"); |
|||
redisAppendCommand(context,"GET foo"); |
|||
redisGetReply(context,&reply); // reply for SET |
|||
freeReplyObject(reply); |
|||
redisGetReply(context,&reply); // reply for GET |
|||
freeReplyObject(reply); |
|||
|
|||
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); |
|||
} |
|||
|
|||
### 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. |
|||
Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) |
|||
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 `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->err) { |
|||
printf("Error: %s\n", c->errstr); |
|||
// handle error |
|||
} |
|||
|
|||
The asynchronous context can hold a disconnect callback function that is called when the |
|||
connection is disconnected (either because of an error or per user request). This function should |
|||
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 `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, |
|||
the disconnect callback is a good point to do so. |
|||
|
|||
Setting the disconnect callback can only be done once per context. For subsequent calls it will |
|||
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: |
|||
|
|||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); |
|||
|
|||
### Sending commands and their callbacks |
|||
|
|||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. |
|||
Therefore, unlike the synchronous API, there is only a single way to send commands. |
|||
Because commands are sent to Redis asynchronously, issuing a command requires a callback function |
|||
that is called when the reply is received. Reply callbacks should have the following prototype: |
|||
|
|||
void(redisAsyncContext *c, void *reply, void *privdata); |
|||
|
|||
The `privdata` argument can be used to curry arbitrary data to the callback from the point where |
|||
the command is initially queued for execution. |
|||
|
|||
The functions that can be used to issue commands in an asynchronous context are: |
|||
|
|||
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); |
|||
|
|||
Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command |
|||
was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection |
|||
is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is |
|||
returned on calls to the `redisAsyncCommand` family. |
|||
|
|||
If the reply for a command with a `NULL` callback is read, it is immediately free'd. When the callback |
|||
for a command is non-`NULL`, it is responsible for cleaning up the reply. |
|||
|
|||
All pending callbacks are called with a `NULL` reply when the context encountered an error. |
|||
|
|||
### Disconnecting |
|||
|
|||
An asynchronous connection can be terminated using: |
|||
|
|||
void redisAsyncDisconnect(redisAsyncContext *ac); |
|||
|
|||
When this function is called, the connection is **not** immediately terminated. Instead, new |
|||
commands are no longer accepted and the connection is only terminated when all pending commands |
|||
have been written to the socket, their respective replies have been read and their respective |
|||
callbacks have been executed. After this, the disconnection callback is executed with the |
|||
`REDIS_OK` status and the context object is free'd. |
|||
|
|||
### Hooking it up to event library *X* |
|||
|
|||
There are a few hooks that need to be set on the context object after it is created. |
|||
See the `adapters/` directory for bindings to *libev* and *libevent*. |
|||
|
|||
## Reply parsing API |
|||
|
|||
Hiredis comes with a reply parsing API that makes it easy for writing higher |
|||
level language bindings. |
|||
|
|||
The reply parsing API consists of the following functions: |
|||
|
|||
redisReader *redisReaderCreate(void); |
|||
void redisReaderFree(redisReader *reader); |
|||
int redisReaderFeed(redisReader *reader, const char *buf, size_t len); |
|||
int redisReaderGetReply(redisReader *reader, void **reply); |
|||
|
|||
### Usage |
|||
|
|||
The function `redisReaderCreate` creates a `redisReader` structure that holds a |
|||
buffer with unparsed data and state for the protocol parser. |
|||
|
|||
Incoming data -- most likely from a socket -- can be placed in the internal |
|||
buffer of the `redisReader` using `redisReaderFeed`. This function will make a |
|||
copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed |
|||
when `redisReaderGetReply` is called. This function returns an integer status |
|||
and a reply object (as described above) via `void **reply`. The returned status |
|||
can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went |
|||
wrong (either a protocol error, or an out of memory error). |
|||
|
|||
### Customizing replies |
|||
|
|||
The function `redisReaderGetReply` creates `redisReply` and makes the function |
|||
argument `reply` point to the created `redisReply` variable. For instance, if |
|||
the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` |
|||
will hold the status as a vanilla C string. However, the functions that are |
|||
responsible for creating instances of the `redisReply` can be customized by |
|||
setting the `fn` field on the `redisReader` struct. This should be done |
|||
immediately after creating the `redisReader`. |
|||
|
|||
For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) |
|||
uses customized reply object functions to create Ruby objects. |
|||
|
|||
## AUTHORS |
|||
|
|||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and |
|||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. |
|||
@ -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->ev.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->ev.addRead = redisAeAddRead; |
|||
ac->ev.delRead = redisAeDelRead; |
|||
ac->ev.addWrite = redisAeAddWrite; |
|||
ac->ev.delWrite = redisAeDelWrite; |
|||
ac->ev.cleanup = redisAeCleanup; |
|||
ac->ev.data = e; |
|||
|
|||
return REDIS_OK; |
|||
} |
|||
|
|||
@ -0,0 +1,113 @@ |
|||
#include <sys/types.h> |
|||
#include <ev.h> |
|||
#include "../hiredis.h" |
|||
#include "../async.h" |
|||
|
|||
typedef struct redisLibevEvents { |
|||
redisAsyncContext *context; |
|||
struct ev_loop *loop; |
|||
int reading, writing; |
|||
ev_io rev, wev; |
|||
} redisLibevEvents; |
|||
|
|||
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(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 = (redisLibevEvents*)privdata; |
|||
struct ev_loop *loop = e->loop; |
|||
((void)loop); |
|||
if (!e->reading) { |
|||
e->reading = 1; |
|||
ev_io_start(EV_A_ &e->rev); |
|||
} |
|||
} |
|||
|
|||
void redisLibevDelRead(void *privdata) { |
|||
redisLibevEvents *e = (redisLibevEvents*)privdata; |
|||
struct ev_loop *loop = e->loop; |
|||
((void)loop); |
|||
if (e->reading) { |
|||
e->reading = 0; |
|||
ev_io_stop(EV_A_ &e->rev); |
|||
} |
|||
} |
|||
|
|||
void redisLibevAddWrite(void *privdata) { |
|||
redisLibevEvents *e = (redisLibevEvents*)privdata; |
|||
struct ev_loop *loop = e->loop; |
|||
((void)loop); |
|||
if (!e->writing) { |
|||
e->writing = 1; |
|||
ev_io_start(EV_A_ &e->wev); |
|||
} |
|||
} |
|||
|
|||
void redisLibevDelWrite(void *privdata) { |
|||
redisLibevEvents *e = (redisLibevEvents*)privdata; |
|||
struct ev_loop *loop = e->loop; |
|||
((void)loop); |
|||
if (e->writing) { |
|||
e->writing = 0; |
|||
ev_io_stop(EV_A_ &e->wev); |
|||
} |
|||
} |
|||
|
|||
void redisLibevCleanup(void *privdata) { |
|||
redisLibevEvents *e = (redisLibevEvents*)privdata; |
|||
redisLibevDelRead(privdata); |
|||
redisLibevDelWrite(privdata); |
|||
free(e); |
|||
} |
|||
|
|||
int redisLibevAttach(EV_P_ redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
redisLibevEvents *e; |
|||
|
|||
/* Nothing should be attached when something is already attached */ |
|||
if (ac->ev.data != NULL) |
|||
return REDIS_ERR; |
|||
|
|||
/* Create container for context and r/w events */ |
|||
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; |
|||
|
|||
/* Register functions to start/stop listening for events */ |
|||
ac->ev.addRead = redisLibevAddRead; |
|||
ac->ev.delRead = redisLibevDelRead; |
|||
ac->ev.addWrite = redisLibevAddWrite; |
|||
ac->ev.delWrite = redisLibevDelWrite; |
|||
ac->ev.cleanup = redisLibevCleanup; |
|||
ac->ev.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; |
|||
} |
|||
|
|||
@ -0,0 +1,75 @@ |
|||
#include <event.h> |
|||
#include "../hiredis.h" |
|||
#include "../async.h" |
|||
|
|||
typedef struct redisLibeventEvents { |
|||
redisAsyncContext *context; |
|||
struct event rev, wev; |
|||
} redisLibeventEvents; |
|||
|
|||
void redisLibeventReadEvent(int fd, short event, void *arg) { |
|||
((void)fd); ((void)event); |
|||
redisLibeventEvents *e = (redisLibeventEvents*)arg; |
|||
redisAsyncHandleRead(e->context); |
|||
} |
|||
|
|||
void redisLibeventWriteEvent(int fd, short event, void *arg) { |
|||
((void)fd); ((void)event); |
|||
redisLibeventEvents *e = (redisLibeventEvents*)arg; |
|||
redisAsyncHandleWrite(e->context); |
|||
} |
|||
|
|||
void redisLibeventAddRead(void *privdata) { |
|||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
|||
event_add(&e->rev,NULL); |
|||
} |
|||
|
|||
void redisLibeventDelRead(void *privdata) { |
|||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
|||
event_del(&e->rev); |
|||
} |
|||
|
|||
void redisLibeventAddWrite(void *privdata) { |
|||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
|||
event_add(&e->wev,NULL); |
|||
} |
|||
|
|||
void redisLibeventDelWrite(void *privdata) { |
|||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
|||
event_del(&e->wev); |
|||
} |
|||
|
|||
void redisLibeventCleanup(void *privdata) { |
|||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
|||
event_del(&e->rev); |
|||
event_del(&e->wev); |
|||
free(e); |
|||
} |
|||
|
|||
int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { |
|||
redisContext *c = &(ac->c); |
|||
redisLibeventEvents *e; |
|||
|
|||
/* Nothing should be attached when something is already attached */ |
|||
if (ac->ev.data != NULL) |
|||
return REDIS_ERR; |
|||
|
|||
/* Create container for context and r/w events */ |
|||
e = (redisLibeventEvents*)malloc(sizeof(*e)); |
|||
e->context = ac; |
|||
|
|||
/* Register functions to start/stop listening for events */ |
|||
ac->ev.addRead = redisLibeventAddRead; |
|||
ac->ev.delRead = redisLibeventDelRead; |
|||
ac->ev.addWrite = redisLibeventAddWrite; |
|||
ac->ev.delWrite = redisLibeventDelWrite; |
|||
ac->ev.cleanup = redisLibeventCleanup; |
|||
ac->ev.data = e; |
|||
|
|||
/* Initialize and install read/write events */ |
|||
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e); |
|||
event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e); |
|||
event_base_set(base,&e->rev); |
|||
event_base_set(base,&e->wev); |
|||
return REDIS_OK; |
|||
} |
|||
@ -0,0 +1,552 @@ |
|||
/* |
|||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
|||
* Copyright (c) 2010-2011, 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#include "fmacros.h" |
|||
#include <string.h> |
|||
#include <strings.h> |
|||
#include <assert.h> |
|||
#include <ctype.h> |
|||
#include "async.h" |
|||
#include "dict.c" |
|||
#include "sds.h" |
|||
|
|||
/* Forward declaration of function in hiredis.c */ |
|||
void __redisAppendCommand(redisContext *c, char *cmd, size_t len); |
|||
|
|||
/* Functions managing dictionary of callbacks for pub/sub. */ |
|||
static unsigned int callbackHash(const void *key) { |
|||
return dictGenHashFunction((unsigned char*)key,sdslen((char*)key)); |
|||
} |
|||
|
|||
static void *callbackValDup(void *privdata, const void *src) { |
|||
((void) privdata); |
|||
redisCallback *dup = malloc(sizeof(*dup)); |
|||
memcpy(dup,src,sizeof(*dup)); |
|||
return dup; |
|||
} |
|||
|
|||
static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { |
|||
int l1, l2; |
|||
((void) privdata); |
|||
|
|||
l1 = sdslen((sds)key1); |
|||
l2 = sdslen((sds)key2); |
|||
if (l1 != l2) return 0; |
|||
return memcmp(key1,key2,l1) == 0; |
|||
} |
|||
|
|||
static void callbackKeyDestructor(void *privdata, void *key) { |
|||
((void) privdata); |
|||
sdsfree((sds)key); |
|||
} |
|||
|
|||
static void callbackValDestructor(void *privdata, void *val) { |
|||
((void) privdata); |
|||
free(val); |
|||
} |
|||
|
|||
static dictType callbackDict = { |
|||
callbackHash, |
|||
NULL, |
|||
callbackValDup, |
|||
callbackKeyCompare, |
|||
callbackKeyDestructor, |
|||
callbackValDestructor |
|||
}; |
|||
|
|||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) { |
|||
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext)); |
|||
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->ev.data = NULL; |
|||
ac->ev.addRead = NULL; |
|||
ac->ev.delRead = NULL; |
|||
ac->ev.addWrite = NULL; |
|||
ac->ev.delWrite = NULL; |
|||
ac->ev.cleanup = NULL; |
|||
|
|||
ac->onConnect = NULL; |
|||
ac->onDisconnect = NULL; |
|||
|
|||
ac->replies.head = NULL; |
|||
ac->replies.tail = NULL; |
|||
ac->sub.invalid.head = NULL; |
|||
ac->sub.invalid.tail = NULL; |
|||
ac->sub.channels = dictCreate(&callbackDict,NULL); |
|||
ac->sub.patterns = dictCreate(&callbackDict,NULL); |
|||
return ac; |
|||
} |
|||
|
|||
/* We want the error field to be accessible directly instead of requiring |
|||
* an indirection to the redisContext struct. */ |
|||
static void __redisAsyncCopyError(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
ac->err = c->err; |
|||
ac->errstr = c->errstr; |
|||
} |
|||
|
|||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { |
|||
redisContext *c = redisConnectNonBlock(ip,port); |
|||
redisAsyncContext *ac = redisAsyncInitialize(c); |
|||
__redisAsyncCopyError(ac); |
|||
return ac; |
|||
} |
|||
|
|||
redisAsyncContext *redisAsyncConnectUnix(const char *path) { |
|||
redisContext *c = redisConnectUnixNonBlock(path); |
|||
redisAsyncContext *ac = redisAsyncInitialize(c); |
|||
__redisAsyncCopyError(ac); |
|||
return ac; |
|||
} |
|||
|
|||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { |
|||
if (ac->onConnect == NULL) { |
|||
ac->onConnect = fn; |
|||
|
|||
/* The common way to detect an established connection is to wait for |
|||
* the first write event to be fired. This assumes the related event |
|||
* library functions are already set. */ |
|||
if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data); |
|||
return REDIS_OK; |
|||
} |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { |
|||
if (ac->onDisconnect == NULL) { |
|||
ac->onDisconnect = fn; |
|||
return REDIS_OK; |
|||
} |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
/* Helper functions to push/shift callbacks */ |
|||
static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { |
|||
redisCallback *cb; |
|||
|
|||
/* Copy callback from stack to heap */ |
|||
cb = malloc(sizeof(*cb)); |
|||
if (source != NULL) { |
|||
memcpy(cb,source,sizeof(*cb)); |
|||
cb->next = NULL; |
|||
} |
|||
|
|||
/* Store callback in list */ |
|||
if (list->head == NULL) |
|||
list->head = cb; |
|||
if (list->tail != NULL) |
|||
list->tail->next = cb; |
|||
list->tail = cb; |
|||
return REDIS_OK; |
|||
} |
|||
|
|||
static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { |
|||
redisCallback *cb = list->head; |
|||
if (cb != NULL) { |
|||
list->head = cb->next; |
|||
if (cb == list->tail) |
|||
list->tail = NULL; |
|||
|
|||
/* Copy callback from heap to stack */ |
|||
if (target != NULL) |
|||
memcpy(target,cb,sizeof(*cb)); |
|||
free(cb); |
|||
return REDIS_OK; |
|||
} |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { |
|||
redisContext *c = &(ac->c); |
|||
if (cb->fn != NULL) { |
|||
c->flags |= REDIS_IN_CALLBACK; |
|||
cb->fn(ac,reply,cb->privdata); |
|||
c->flags &= ~REDIS_IN_CALLBACK; |
|||
} |
|||
} |
|||
|
|||
/* Helper function to free the context. */ |
|||
static void __redisAsyncFree(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
redisCallback cb; |
|||
dictIterator *it; |
|||
dictEntry *de; |
|||
|
|||
/* Execute pending callbacks with NULL reply. */ |
|||
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) |
|||
__redisRunCallback(ac,&cb,NULL); |
|||
|
|||
/* Execute callbacks for invalid commands */ |
|||
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) |
|||
__redisRunCallback(ac,&cb,NULL); |
|||
|
|||
/* Run subscription callbacks callbacks with NULL reply */ |
|||
it = dictGetIterator(ac->sub.channels); |
|||
while ((de = dictNext(it)) != NULL) |
|||
__redisRunCallback(ac,dictGetEntryVal(de),NULL); |
|||
dictReleaseIterator(it); |
|||
dictRelease(ac->sub.channels); |
|||
|
|||
it = dictGetIterator(ac->sub.patterns); |
|||
while ((de = dictNext(it)) != NULL) |
|||
__redisRunCallback(ac,dictGetEntryVal(de),NULL); |
|||
dictReleaseIterator(it); |
|||
dictRelease(ac->sub.patterns); |
|||
|
|||
/* Signal event lib to clean up */ |
|||
if (ac->ev.cleanup) ac->ev.cleanup(ac->ev.data); |
|||
|
|||
/* Execute disconnect callback. When redisAsyncFree() initiated destroying |
|||
* this context, the status will always be REDIS_OK. */ |
|||
if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { |
|||
if (c->flags & REDIS_FREEING) { |
|||
ac->onDisconnect(ac,REDIS_OK); |
|||
} else { |
|||
ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); |
|||
} |
|||
} |
|||
|
|||
/* Cleanup self */ |
|||
redisFree(c); |
|||
} |
|||
|
|||
/* Free the async context. When this function is called from a callback, |
|||
* control needs to be returned to redisProcessCallbacks() before actual |
|||
* free'ing. To do so, a flag is set on the context which is picked up by |
|||
* redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ |
|||
void redisAsyncFree(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
c->flags |= REDIS_FREEING; |
|||
if (!(c->flags & REDIS_IN_CALLBACK)) |
|||
__redisAsyncFree(ac); |
|||
} |
|||
|
|||
/* Helper function to make the disconnect happen and clean up. */ |
|||
static void __redisAsyncDisconnect(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
|
|||
/* Make sure error is accessible if there is any */ |
|||
__redisAsyncCopyError(ac); |
|||
|
|||
if (ac->err == 0) { |
|||
/* For clean disconnects, there should be no pending callbacks. */ |
|||
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); |
|||
} else { |
|||
/* Disconnection is caused by an error, make sure that pending |
|||
* callbacks cannot call new commands. */ |
|||
c->flags |= REDIS_DISCONNECTING; |
|||
} |
|||
|
|||
/* For non-clean disconnects, __redisAsyncFree() will execute pending |
|||
* callbacks with a NULL-reply. */ |
|||
__redisAsyncFree(ac); |
|||
} |
|||
|
|||
/* Tries to do a clean disconnect from Redis, meaning it stops new commands |
|||
* from being issued, but tries to flush the output buffer and execute |
|||
* callbacks for all remaining replies. When this function is called from a |
|||
* callback, there might be more replies and we can safely defer disconnecting |
|||
* to redisProcessCallbacks(). Otherwise, we can only disconnect immediately |
|||
* when there are no pending callbacks. */ |
|||
void redisAsyncDisconnect(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
c->flags |= REDIS_DISCONNECTING; |
|||
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) |
|||
__redisAsyncDisconnect(ac); |
|||
} |
|||
|
|||
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { |
|||
redisContext *c = &(ac->c); |
|||
dict *callbacks; |
|||
dictEntry *de; |
|||
int pvariant; |
|||
char *stype; |
|||
sds sname; |
|||
|
|||
/* Custom reply functions are not supported for pub/sub. This will fail |
|||
* very hard when they are used... */ |
|||
if (reply->type == REDIS_REPLY_ARRAY) { |
|||
assert(reply->elements >= 2); |
|||
assert(reply->element[0]->type == REDIS_REPLY_STRING); |
|||
stype = reply->element[0]->str; |
|||
pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; |
|||
|
|||
if (pvariant) |
|||
callbacks = ac->sub.patterns; |
|||
else |
|||
callbacks = ac->sub.channels; |
|||
|
|||
/* Locate the right callback */ |
|||
assert(reply->element[1]->type == REDIS_REPLY_STRING); |
|||
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); |
|||
de = dictFind(callbacks,sname); |
|||
if (de != NULL) { |
|||
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); |
|||
|
|||
/* If this is an unsubscribe message, remove it. */ |
|||
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { |
|||
dictDelete(callbacks,sname); |
|||
|
|||
/* If this was the last unsubscribe message, revert to |
|||
* non-subscribe mode. */ |
|||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER); |
|||
if (reply->element[2]->integer == 0) |
|||
c->flags &= ~REDIS_SUBSCRIBED; |
|||
} |
|||
} |
|||
sdsfree(sname); |
|||
} else { |
|||
/* Shift callback for invalid commands. */ |
|||
__redisShiftCallback(&ac->sub.invalid,dstcb); |
|||
} |
|||
return REDIS_OK; |
|||
} |
|||
|
|||
void redisProcessCallbacks(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
redisCallback cb; |
|||
void *reply = NULL; |
|||
int status; |
|||
|
|||
while((status = redisGetReply(c,&reply)) == REDIS_OK) { |
|||
if (reply == NULL) { |
|||
/* When the connection is being disconnected and there are |
|||
* no more replies, this is the cue to really disconnect. */ |
|||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { |
|||
__redisAsyncDisconnect(ac); |
|||
return; |
|||
} |
|||
|
|||
/* When the connection is not being disconnected, simply stop |
|||
* trying to get replies and wait for the next loop tick. */ |
|||
break; |
|||
} |
|||
|
|||
/* Even if the context is subscribed, pending regular callbacks will |
|||
* get a reply before pub/sub messages arrive. */ |
|||
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { |
|||
/* A spontaneous reply in a not-subscribed context can only be the |
|||
* error reply that is sent when a new connection exceeds the |
|||
* maximum number of allowed connections on the server side. This |
|||
* is seen as an error instead of a regular reply because the |
|||
* server closes the connection after sending it. To prevent the |
|||
* error from being overwritten by an EOF error the connection is |
|||
* closed here. See issue #43. */ |
|||
if ( !(c->flags & REDIS_SUBSCRIBED) && ((redisReply*)reply)->type == REDIS_REPLY_ERROR ) { |
|||
c->err = REDIS_ERR_OTHER; |
|||
snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); |
|||
__redisAsyncDisconnect(ac); |
|||
return; |
|||
} |
|||
/* No more regular callbacks and no errors, the context *must* be subscribed. */ |
|||
assert(c->flags & REDIS_SUBSCRIBED); |
|||
__redisGetSubscribeCallback(ac,reply,&cb); |
|||
} |
|||
|
|||
if (cb.fn != NULL) { |
|||
__redisRunCallback(ac,&cb,reply); |
|||
c->reader->fn->freeObject(reply); |
|||
|
|||
/* Proceed with free'ing when redisAsyncFree() was called. */ |
|||
if (c->flags & REDIS_FREEING) { |
|||
__redisAsyncFree(ac); |
|||
return; |
|||
} |
|||
} else { |
|||
/* No callback for this reply. This can either be a NULL callback, |
|||
* or there were no callbacks to begin with. Either way, don't |
|||
* abort with an error, but simply ignore it because the client |
|||
* doesn't know what the server will spit out over the wire. */ |
|||
c->reader->fn->freeObject(reply); |
|||
} |
|||
} |
|||
|
|||
/* Disconnect when there was an error reading the reply */ |
|||
if (status != REDIS_OK) |
|||
__redisAsyncDisconnect(ac); |
|||
} |
|||
|
|||
/* This function should be called when the socket is readable. |
|||
* It processes all replies that can be read and executes their callbacks. |
|||
*/ |
|||
void redisAsyncHandleRead(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
|
|||
if (redisBufferRead(c) == REDIS_ERR) { |
|||
__redisAsyncDisconnect(ac); |
|||
} else { |
|||
/* Always re-schedule reads */ |
|||
if (ac->ev.addRead) ac->ev.addRead(ac->ev.data); |
|||
redisProcessCallbacks(ac); |
|||
} |
|||
} |
|||
|
|||
void redisAsyncHandleWrite(redisAsyncContext *ac) { |
|||
redisContext *c = &(ac->c); |
|||
int done = 0; |
|||
|
|||
if (redisBufferWrite(c,&done) == REDIS_ERR) { |
|||
__redisAsyncDisconnect(ac); |
|||
} else { |
|||
/* Continue writing when not done, stop writing otherwise */ |
|||
if (!done) { |
|||
if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data); |
|||
} else { |
|||
if (ac->ev.delWrite) ac->ev.delWrite(ac->ev.data); |
|||
} |
|||
|
|||
/* Always schedule reads after writes */ |
|||
if (ac->ev.addRead) ac->ev.addRead(ac->ev.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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* Sets a pointer to the first argument and its length starting at p. Returns |
|||
* the number of bytes to skip to get to the following argument. */ |
|||
static char *nextArgument(char *start, char **str, size_t *len) { |
|||
char *p = start; |
|||
if (p[0] != '$') { |
|||
p = strchr(p,'$'); |
|||
if (p == NULL) return NULL; |
|||
} |
|||
|
|||
*len = (int)strtol(p+1,NULL,10); |
|||
p = strchr(p,'\r'); |
|||
assert(p); |
|||
*str = p+2; |
|||
return p+2+(*len)+2; |
|||
} |
|||
|
|||
/* Helper function for the redisAsyncCommand* family of functions. Writes a |
|||
* formatted command to the output buffer and registers the provided callback |
|||
* function with the context. */ |
|||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) { |
|||
redisContext *c = &(ac->c); |
|||
redisCallback cb; |
|||
int pvariant, hasnext; |
|||
char *cstr, *astr; |
|||
size_t clen, alen; |
|||
char *p; |
|||
sds sname; |
|||
|
|||
/* Don't accept new commands when the connection is about to be closed. */ |
|||
if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; |
|||
|
|||
/* Setup callback */ |
|||
cb.fn = fn; |
|||
cb.privdata = privdata; |
|||
|
|||
/* Find out which command will be appended. */ |
|||
p = nextArgument(cmd,&cstr,&clen); |
|||
assert(p != NULL); |
|||
hasnext = (p[0] == '$'); |
|||
pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; |
|||
cstr += pvariant; |
|||
clen -= pvariant; |
|||
|
|||
if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { |
|||
c->flags |= REDIS_SUBSCRIBED; |
|||
|
|||
/* Add every channel/pattern to the list of subscription callbacks. */ |
|||
while ((p = nextArgument(p,&astr,&alen)) != NULL) { |
|||
sname = sdsnewlen(astr,alen); |
|||
if (pvariant) |
|||
dictReplace(ac->sub.patterns,sname,&cb); |
|||
else |
|||
dictReplace(ac->sub.channels,sname,&cb); |
|||
} |
|||
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { |
|||
/* It is only useful to call (P)UNSUBSCRIBE when the context is |
|||
* subscribed to one or more channels or patterns. */ |
|||
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; |
|||
|
|||
/* (P)UNSUBSCRIBE does not have its own response: every channel or |
|||
* pattern that is unsubscribed will receive a message. This means we |
|||
* should not append a callback function for this command. */ |
|||
} else { |
|||
if (c->flags & REDIS_SUBSCRIBED) |
|||
/* This will likely result in an error reply, but it needs to be |
|||
* received and passed to the callback. */ |
|||
__redisPushCallback(&ac->sub.invalid,&cb); |
|||
else |
|||
__redisPushCallback(&ac->replies,&cb); |
|||
} |
|||
|
|||
__redisAppendCommand(c,cmd,len); |
|||
|
|||
/* Always schedule a write when the write buffer is non-empty */ |
|||
if (ac->ev.addWrite) ac->ev.addWrite(ac->ev.data); |
|||
|
|||
return REDIS_OK; |
|||
} |
|||
|
|||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { |
|||
char *cmd; |
|||
int len; |
|||
int status; |
|||
len = redisvFormatCommand(&cmd,format,ap); |
|||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); |
|||
free(cmd); |
|||
return status; |
|||
} |
|||
|
|||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { |
|||
va_list ap; |
|||
int status; |
|||
va_start(ap,format); |
|||
status = redisvAsyncCommand(ac,fn,privdata,format,ap); |
|||
va_end(ap); |
|||
return status; |
|||
} |
|||
|
|||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { |
|||
char *cmd; |
|||
int len; |
|||
int status; |
|||
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); |
|||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); |
|||
free(cmd); |
|||
return status; |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
/* |
|||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
|||
* Copyright (c) 2010-2011, 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#ifndef __HIREDIS_ASYNC_H |
|||
#define __HIREDIS_ASYNC_H |
|||
#include "hiredis.h" |
|||
|
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ |
|||
struct dict; /* dictionary header is included in async.c */ |
|||
|
|||
/* Reply callback prototype and container */ |
|||
typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); |
|||
typedef struct redisCallback { |
|||
struct redisCallback *next; /* simple singly linked list */ |
|||
redisCallbackFn *fn; |
|||
void *privdata; |
|||
} redisCallback; |
|||
|
|||
/* List of callbacks for either regular replies or pub/sub */ |
|||
typedef struct redisCallbackList { |
|||
redisCallback *head, *tail; |
|||
} redisCallbackList; |
|||
|
|||
/* 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 { |
|||
/* Hold the regular context, so it can be realloc'ed. */ |
|||
redisContext c; |
|||
|
|||
/* Setup error flags so they can be used directly. */ |
|||
int err; |
|||
char *errstr; |
|||
|
|||
/* Not used by hiredis */ |
|||
void *data; |
|||
|
|||
/* Event library data and hooks */ |
|||
struct { |
|||
void *data; |
|||
|
|||
/* Hooks that are called when the library expects to start |
|||
* reading/writing. These functions should be idempotent. */ |
|||
void (*addRead)(void *privdata); |
|||
void (*delRead)(void *privdata); |
|||
void (*addWrite)(void *privdata); |
|||
void (*delWrite)(void *privdata); |
|||
void (*cleanup)(void *privdata); |
|||
} ev; |
|||
|
|||
/* 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; |
|||
|
|||
/* Regular command callbacks */ |
|||
redisCallbackList replies; |
|||
|
|||
/* Subscription callbacks */ |
|||
struct { |
|||
redisCallbackList invalid; |
|||
struct dict *channels; |
|||
struct dict *patterns; |
|||
} sub; |
|||
} redisAsyncContext; |
|||
|
|||
/* Functions that proxy to hiredis */ |
|||
redisAsyncContext *redisAsyncConnect(const char *ip, int port); |
|||
redisAsyncContext *redisAsyncConnectUnix(const char *path); |
|||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); |
|||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); |
|||
void redisAsyncDisconnect(redisAsyncContext *ac); |
|||
void redisAsyncFree(redisAsyncContext *ac); |
|||
|
|||
/* Handle read/write events */ |
|||
void redisAsyncHandleRead(redisAsyncContext *ac); |
|||
void redisAsyncHandleWrite(redisAsyncContext *ac); |
|||
|
|||
/* Command functions for an async context. Write the command to the |
|||
* output buffer and register the provided callback. */ |
|||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); |
|||
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 |
|||
@ -0,0 +1,338 @@ |
|||
/* Hash table implementation. |
|||
* |
|||
* This file implements in memory hash tables with insert/del/replace/find/ |
|||
* get-random-element operations. Hash tables will auto resize if needed |
|||
* tables of power of two in size are used, collisions are handled by |
|||
* chaining. See the source code for more information... :) |
|||
* |
|||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#include "fmacros.h" |
|||
#include <stdlib.h> |
|||
#include <assert.h> |
|||
#include <limits.h> |
|||
#include "dict.h" |
|||
|
|||
/* -------------------------- private prototypes ---------------------------- */ |
|||
|
|||
static int _dictExpandIfNeeded(dict *ht); |
|||
static unsigned long _dictNextPower(unsigned long size); |
|||
static int _dictKeyIndex(dict *ht, const void *key); |
|||
static int _dictInit(dict *ht, dictType *type, void *privDataPtr); |
|||
|
|||
/* -------------------------- hash functions -------------------------------- */ |
|||
|
|||
/* Generic hash function (a popular one from Bernstein). |
|||
* I tested a few and this was the best. */ |
|||
static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { |
|||
unsigned int hash = 5381; |
|||
|
|||
while (len--) |
|||
hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ |
|||
return hash; |
|||
} |
|||
|
|||
/* ----------------------------- API implementation ------------------------- */ |
|||
|
|||
/* Reset an hashtable already initialized with ht_init(). |
|||
* NOTE: This function should only called by ht_destroy(). */ |
|||
static void _dictReset(dict *ht) { |
|||
ht->table = NULL; |
|||
ht->size = 0; |
|||
ht->sizemask = 0; |
|||
ht->used = 0; |
|||
} |
|||
|
|||
/* Create a new hash table */ |
|||
static dict *dictCreate(dictType *type, void *privDataPtr) { |
|||
dict *ht = malloc(sizeof(*ht)); |
|||
_dictInit(ht,type,privDataPtr); |
|||
return ht; |
|||
} |
|||
|
|||
/* Initialize the hash table */ |
|||
static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { |
|||
_dictReset(ht); |
|||
ht->type = type; |
|||
ht->privdata = privDataPtr; |
|||
return DICT_OK; |
|||
} |
|||
|
|||
/* Expand or create the hashtable */ |
|||
static int dictExpand(dict *ht, unsigned long size) { |
|||
dict n; /* the new hashtable */ |
|||
unsigned long realsize = _dictNextPower(size), i; |
|||
|
|||
/* the size is invalid if it is smaller than the number of |
|||
* elements already inside the hashtable */ |
|||
if (ht->used > size) |
|||
return DICT_ERR; |
|||
|
|||
_dictInit(&n, ht->type, ht->privdata); |
|||
n.size = realsize; |
|||
n.sizemask = realsize-1; |
|||
n.table = calloc(realsize,sizeof(dictEntry*)); |
|||
|
|||
/* Copy all the elements from the old to the new table: |
|||
* note that if the old hash table is empty ht->size is zero, |
|||
* so dictExpand just creates an hash table. */ |
|||
n.used = ht->used; |
|||
for (i = 0; i < ht->size && ht->used > 0; i++) { |
|||
dictEntry *he, *nextHe; |
|||
|
|||
if (ht->table[i] == NULL) continue; |
|||
|
|||
/* For each hash entry on this slot... */ |
|||
he = ht->table[i]; |
|||
while(he) { |
|||
unsigned int h; |
|||
|
|||
nextHe = he->next; |
|||
/* Get the new element index */ |
|||
h = dictHashKey(ht, he->key) & n.sizemask; |
|||
he->next = n.table[h]; |
|||
n.table[h] = he; |
|||
ht->used--; |
|||
/* Pass to the next element */ |
|||
he = nextHe; |
|||
} |
|||
} |
|||
assert(ht->used == 0); |
|||
free(ht->table); |
|||
|
|||
/* Remap the new hashtable in the old */ |
|||
*ht = n; |
|||
return DICT_OK; |
|||
} |
|||
|
|||
/* Add an element to the target hash table */ |
|||
static int dictAdd(dict *ht, void *key, void *val) { |
|||
int index; |
|||
dictEntry *entry; |
|||
|
|||
/* Get the index of the new element, or -1 if |
|||
* the element already exists. */ |
|||
if ((index = _dictKeyIndex(ht, key)) == -1) |
|||
return DICT_ERR; |
|||
|
|||
/* Allocates the memory and stores key */ |
|||
entry = malloc(sizeof(*entry)); |
|||
entry->next = ht->table[index]; |
|||
ht->table[index] = entry; |
|||
|
|||
/* Set the hash entry fields. */ |
|||
dictSetHashKey(ht, entry, key); |
|||
dictSetHashVal(ht, entry, val); |
|||
ht->used++; |
|||
return DICT_OK; |
|||
} |
|||
|
|||
/* Add an element, discarding the old if the key already exists. |
|||
* Return 1 if the key was added from scratch, 0 if there was already an |
|||
* element with such key and dictReplace() just performed a value update |
|||
* operation. */ |
|||
static int dictReplace(dict *ht, void *key, void *val) { |
|||
dictEntry *entry, auxentry; |
|||
|
|||
/* Try to add the element. If the key |
|||
* does not exists dictAdd will suceed. */ |
|||
if (dictAdd(ht, key, val) == DICT_OK) |
|||
return 1; |
|||
/* It already exists, get the entry */ |
|||
entry = dictFind(ht, key); |
|||
/* Free the old value and set the new one */ |
|||
/* Set the new value and free the old one. Note that it is important |
|||
* to do that in this order, as the value may just be exactly the same |
|||
* as the previous one. In this context, think to reference counting, |
|||
* you want to increment (set), and then decrement (free), and not the |
|||
* reverse. */ |
|||
auxentry = *entry; |
|||
dictSetHashVal(ht, entry, val); |
|||
dictFreeEntryVal(ht, &auxentry); |
|||
return 0; |
|||
} |
|||
|
|||
/* Search and remove an element */ |
|||
static int dictDelete(dict *ht, const void *key) { |
|||
unsigned int h; |
|||
dictEntry *de, *prevde; |
|||
|
|||
if (ht->size == 0) |
|||
return DICT_ERR; |
|||
h = dictHashKey(ht, key) & ht->sizemask; |
|||
de = ht->table[h]; |
|||
|
|||
prevde = NULL; |
|||
while(de) { |
|||
if (dictCompareHashKeys(ht,key,de->key)) { |
|||
/* Unlink the element from the list */ |
|||
if (prevde) |
|||
prevde->next = de->next; |
|||
else |
|||
ht->table[h] = de->next; |
|||
|
|||
dictFreeEntryKey(ht,de); |
|||
dictFreeEntryVal(ht,de); |
|||
free(de); |
|||
ht->used--; |
|||
return DICT_OK; |
|||
} |
|||
prevde = de; |
|||
de = de->next; |
|||
} |
|||
return DICT_ERR; /* not found */ |
|||
} |
|||
|
|||
/* Destroy an entire hash table */ |
|||
static int _dictClear(dict *ht) { |
|||
unsigned long i; |
|||
|
|||
/* Free all the elements */ |
|||
for (i = 0; i < ht->size && ht->used > 0; i++) { |
|||
dictEntry *he, *nextHe; |
|||
|
|||
if ((he = ht->table[i]) == NULL) continue; |
|||
while(he) { |
|||
nextHe = he->next; |
|||
dictFreeEntryKey(ht, he); |
|||
dictFreeEntryVal(ht, he); |
|||
free(he); |
|||
ht->used--; |
|||
he = nextHe; |
|||
} |
|||
} |
|||
/* Free the table and the allocated cache structure */ |
|||
free(ht->table); |
|||
/* Re-initialize the table */ |
|||
_dictReset(ht); |
|||
return DICT_OK; /* never fails */ |
|||
} |
|||
|
|||
/* Clear & Release the hash table */ |
|||
static void dictRelease(dict *ht) { |
|||
_dictClear(ht); |
|||
free(ht); |
|||
} |
|||
|
|||
static dictEntry *dictFind(dict *ht, const void *key) { |
|||
dictEntry *he; |
|||
unsigned int h; |
|||
|
|||
if (ht->size == 0) return NULL; |
|||
h = dictHashKey(ht, key) & ht->sizemask; |
|||
he = ht->table[h]; |
|||
while(he) { |
|||
if (dictCompareHashKeys(ht, key, he->key)) |
|||
return he; |
|||
he = he->next; |
|||
} |
|||
return NULL; |
|||
} |
|||
|
|||
static dictIterator *dictGetIterator(dict *ht) { |
|||
dictIterator *iter = malloc(sizeof(*iter)); |
|||
|
|||
iter->ht = ht; |
|||
iter->index = -1; |
|||
iter->entry = NULL; |
|||
iter->nextEntry = NULL; |
|||
return iter; |
|||
} |
|||
|
|||
static dictEntry *dictNext(dictIterator *iter) { |
|||
while (1) { |
|||
if (iter->entry == NULL) { |
|||
iter->index++; |
|||
if (iter->index >= |
|||
(signed)iter->ht->size) break; |
|||
iter->entry = iter->ht->table[iter->index]; |
|||
} else { |
|||
iter->entry = iter->nextEntry; |
|||
} |
|||
if (iter->entry) { |
|||
/* We need to save the 'next' here, the iterator user |
|||
* may delete the entry we are returning. */ |
|||
iter->nextEntry = iter->entry->next; |
|||
return iter->entry; |
|||
} |
|||
} |
|||
return NULL; |
|||
} |
|||
|
|||
static void dictReleaseIterator(dictIterator *iter) { |
|||
free(iter); |
|||
} |
|||
|
|||
/* ------------------------- private functions ------------------------------ */ |
|||
|
|||
/* Expand the hash table if needed */ |
|||
static int _dictExpandIfNeeded(dict *ht) { |
|||
/* If the hash table is empty expand it to the intial size, |
|||
* if the table is "full" dobule its size. */ |
|||
if (ht->size == 0) |
|||
return dictExpand(ht, DICT_HT_INITIAL_SIZE); |
|||
if (ht->used == ht->size) |
|||
return dictExpand(ht, ht->size*2); |
|||
return DICT_OK; |
|||
} |
|||
|
|||
/* Our hash table capability is a power of two */ |
|||
static unsigned long _dictNextPower(unsigned long size) { |
|||
unsigned long i = DICT_HT_INITIAL_SIZE; |
|||
|
|||
if (size >= LONG_MAX) return LONG_MAX; |
|||
while(1) { |
|||
if (i >= size) |
|||
return i; |
|||
i *= 2; |
|||
} |
|||
} |
|||
|
|||
/* Returns the index of a free slot that can be populated with |
|||
* an hash entry for the given 'key'. |
|||
* If the key already exists, -1 is returned. */ |
|||
static int _dictKeyIndex(dict *ht, const void *key) { |
|||
unsigned int h; |
|||
dictEntry *he; |
|||
|
|||
/* Expand the hashtable if needed */ |
|||
if (_dictExpandIfNeeded(ht) == DICT_ERR) |
|||
return -1; |
|||
/* Compute the key hash value */ |
|||
h = dictHashKey(ht, key) & ht->sizemask; |
|||
/* Search if this slot does not already contain the given key */ |
|||
he = ht->table[h]; |
|||
while(he) { |
|||
if (dictCompareHashKeys(ht, key, he->key)) |
|||
return -1; |
|||
he = he->next; |
|||
} |
|||
return h; |
|||
} |
|||
|
|||
@ -0,0 +1,126 @@ |
|||
/* Hash table implementation. |
|||
* |
|||
* This file implements in memory hash tables with insert/del/replace/find/ |
|||
* get-random-element operations. Hash tables will auto resize if needed |
|||
* tables of power of two in size are used, collisions are handled by |
|||
* chaining. See the source code for more information... :) |
|||
* |
|||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#ifndef __DICT_H |
|||
#define __DICT_H |
|||
|
|||
#define DICT_OK 0 |
|||
#define DICT_ERR 1 |
|||
|
|||
/* Unused arguments generate annoying warnings... */ |
|||
#define DICT_NOTUSED(V) ((void) V) |
|||
|
|||
typedef struct dictEntry { |
|||
void *key; |
|||
void *val; |
|||
struct dictEntry *next; |
|||
} dictEntry; |
|||
|
|||
typedef struct dictType { |
|||
unsigned int (*hashFunction)(const void *key); |
|||
void *(*keyDup)(void *privdata, const void *key); |
|||
void *(*valDup)(void *privdata, const void *obj); |
|||
int (*keyCompare)(void *privdata, const void *key1, const void *key2); |
|||
void (*keyDestructor)(void *privdata, void *key); |
|||
void (*valDestructor)(void *privdata, void *obj); |
|||
} dictType; |
|||
|
|||
typedef struct dict { |
|||
dictEntry **table; |
|||
dictType *type; |
|||
unsigned long size; |
|||
unsigned long sizemask; |
|||
unsigned long used; |
|||
void *privdata; |
|||
} dict; |
|||
|
|||
typedef struct dictIterator { |
|||
dict *ht; |
|||
int index; |
|||
dictEntry *entry, *nextEntry; |
|||
} dictIterator; |
|||
|
|||
/* This is the initial size of every hash table */ |
|||
#define DICT_HT_INITIAL_SIZE 4 |
|||
|
|||
/* ------------------------------- Macros ------------------------------------*/ |
|||
#define dictFreeEntryVal(ht, entry) \ |
|||
if ((ht)->type->valDestructor) \ |
|||
(ht)->type->valDestructor((ht)->privdata, (entry)->val) |
|||
|
|||
#define dictSetHashVal(ht, entry, _val_) do { \ |
|||
if ((ht)->type->valDup) \ |
|||
entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ |
|||
else \ |
|||
entry->val = (_val_); \ |
|||
} while(0) |
|||
|
|||
#define dictFreeEntryKey(ht, entry) \ |
|||
if ((ht)->type->keyDestructor) \ |
|||
(ht)->type->keyDestructor((ht)->privdata, (entry)->key) |
|||
|
|||
#define dictSetHashKey(ht, entry, _key_) do { \ |
|||
if ((ht)->type->keyDup) \ |
|||
entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ |
|||
else \ |
|||
entry->key = (_key_); \ |
|||
} while(0) |
|||
|
|||
#define dictCompareHashKeys(ht, key1, key2) \ |
|||
(((ht)->type->keyCompare) ? \ |
|||
(ht)->type->keyCompare((ht)->privdata, key1, key2) : \ |
|||
(key1) == (key2)) |
|||
|
|||
#define dictHashKey(ht, key) (ht)->type->hashFunction(key) |
|||
|
|||
#define dictGetEntryKey(he) ((he)->key) |
|||
#define dictGetEntryVal(he) ((he)->val) |
|||
#define dictSlots(ht) ((ht)->size) |
|||
#define dictSize(ht) ((ht)->used) |
|||
|
|||
/* API */ |
|||
static unsigned int dictGenHashFunction(const unsigned char *buf, int len); |
|||
static dict *dictCreate(dictType *type, void *privDataPtr); |
|||
static int dictExpand(dict *ht, unsigned long size); |
|||
static int dictAdd(dict *ht, void *key, void *val); |
|||
static int dictReplace(dict *ht, void *key, void *val); |
|||
static int dictDelete(dict *ht, const void *key); |
|||
static void dictRelease(dict *ht); |
|||
static dictEntry * dictFind(dict *ht, const void *key); |
|||
static dictIterator *dictGetIterator(dict *ht); |
|||
static dictEntry *dictNext(dictIterator *iter); |
|||
static void dictReleaseIterator(dictIterator *iter); |
|||
|
|||
#endif /* __DICT_H */ |
|||
@ -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; |
|||
} |
|||
|
|||
@ -0,0 +1,47 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <signal.h> |
|||
#include "hiredis.h" |
|||
#include "async.h" |
|||
#include "adapters/libev.h" |
|||
|
|||
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"); |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
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(EV_DEFAULT_ 0); |
|||
return 0; |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <signal.h> |
|||
#include "hiredis.h" |
|||
#include "async.h" |
|||
#include "adapters/libevent.h" |
|||
|
|||
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"); |
|||
} |
|||
|
|||
int main (int argc, char **argv) { |
|||
signal(SIGPIPE, SIG_IGN); |
|||
struct event_base *base = event_base_new(); |
|||
|
|||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); |
|||
if (c->err) { |
|||
/* Let *c leak for now... */ |
|||
printf("Error: %s\n", c->errstr); |
|||
return 1; |
|||
} |
|||
|
|||
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"); |
|||
event_base_dispatch(base); |
|||
return 0; |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
|
|||
#include "hiredis.h" |
|||
|
|||
int main(void) { |
|||
unsigned int j; |
|||
redisContext *c; |
|||
redisReply *reply; |
|||
|
|||
struct timeval timeout = { 1, 500000 }; // 1.5 seconds |
|||
c = redisConnectWithTimeout((char*)"127.0.0.2", 6379, timeout); |
|||
if (c->err) { |
|||
printf("Connection error: %s\n", c->errstr); |
|||
exit(1); |
|||
} |
|||
|
|||
/* PING server */ |
|||
reply = redisCommand(c,"PING"); |
|||
printf("PING: %s\n", reply->str); |
|||
freeReplyObject(reply); |
|||
|
|||
/* Set a key */ |
|||
reply = redisCommand(c,"SET %s %s", "foo", "hello world"); |
|||
printf("SET: %s\n", reply->str); |
|||
freeReplyObject(reply); |
|||
|
|||
/* Set a key using binary safe API */ |
|||
reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5); |
|||
printf("SET (binary API): %s\n", reply->str); |
|||
freeReplyObject(reply); |
|||
|
|||
/* Try a GET and two INCR */ |
|||
reply = redisCommand(c,"GET foo"); |
|||
printf("GET foo: %s\n", reply->str); |
|||
freeReplyObject(reply); |
|||
|
|||
reply = redisCommand(c,"INCR counter"); |
|||
printf("INCR counter: %lld\n", reply->integer); |
|||
freeReplyObject(reply); |
|||
/* again ... */ |
|||
reply = redisCommand(c,"INCR counter"); |
|||
printf("INCR counter: %lld\n", reply->integer); |
|||
freeReplyObject(reply); |
|||
|
|||
/* Create a list of numbers, from 0 to 9 */ |
|||
reply = redisCommand(c,"DEL mylist"); |
|||
freeReplyObject(reply); |
|||
for (j = 0; j < 10; j++) { |
|||
char buf[64]; |
|||
|
|||
snprintf(buf,64,"%d",j); |
|||
reply = redisCommand(c,"LPUSH mylist element-%s", buf); |
|||
freeReplyObject(reply); |
|||
} |
|||
|
|||
/* Let's check what we have inside the list */ |
|||
reply = redisCommand(c,"LRANGE mylist 0 -1"); |
|||
if (reply->type == REDIS_REPLY_ARRAY) { |
|||
for (j = 0; j < reply->elements; j++) { |
|||
printf("%u) %s\n", j, reply->element[j]->str); |
|||
} |
|||
} |
|||
freeReplyObject(reply); |
|||
|
|||
return 0; |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
#ifndef __HIREDIS_FMACRO_H |
|||
#define __HIREDIS_FMACRO_H |
|||
|
|||
#if !defined(_BSD_SOURCE) |
|||
#define _BSD_SOURCE |
|||
#endif |
|||
|
|||
#if defined(__sun__) |
|||
#define _POSIX_C_SOURCE 200112L |
|||
#elif defined(__linux__) |
|||
#define _XOPEN_SOURCE 600 |
|||
#else |
|||
#define _XOPEN_SOURCE |
|||
#endif |
|||
|
|||
#endif |
|||
1237
contrib/hiredis/hiredis.c
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,204 @@ |
|||
/* |
|||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
|||
* Copyright (c) 2010-2011, 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#ifndef __HIREDIS_H |
|||
#define __HIREDIS_H |
|||
#include <stdio.h> /* for size_t */ |
|||
#include <stdarg.h> /* for va_list */ |
|||
#include <sys/time.h> /* for struct timeval */ |
|||
|
|||
#define HIREDIS_MAJOR 0 |
|||
#define HIREDIS_MINOR 10 |
|||
#define HIREDIS_PATCH 1 |
|||
|
|||
#define REDIS_ERR -1 |
|||
#define REDIS_OK 0 |
|||
|
|||
/* When an error occurs, the err flag in a context is set to hold the type of |
|||
* error that occured. REDIS_ERR_IO means there was an I/O error and you |
|||
* should use the "errno" variable to find out what is wrong. |
|||
* For other values, the "errstr" field will hold a description. */ |
|||
#define REDIS_ERR_IO 1 /* Error in read or write */ |
|||
#define REDIS_ERR_EOF 3 /* End of file */ |
|||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ |
|||
#define REDIS_ERR_OOM 5 /* Out of memory */ |
|||
#define REDIS_ERR_OTHER 2 /* Everything else... */ |
|||
|
|||
/* Connection type can be blocking or non-blocking and is set in the |
|||
* least significant bit of the flags field in redisContext. */ |
|||
#define REDIS_BLOCK 0x1 |
|||
|
|||
/* Connection may be disconnected before being free'd. The second bit |
|||
* in the flags field is set when the context is connected. */ |
|||
#define REDIS_CONNECTED 0x2 |
|||
|
|||
/* The async API might try to disconnect cleanly and flush the output |
|||
* buffer and read all subsequent replies before disconnecting. |
|||
* This flag means no new commands can come in and the connection |
|||
* should be terminated once all replies have been read. */ |
|||
#define REDIS_DISCONNECTING 0x4 |
|||
|
|||
/* Flag specific to the async API which means that the context should be clean |
|||
* up as soon as possible. */ |
|||
#define REDIS_FREEING 0x8 |
|||
|
|||
/* Flag that is set when an async callback is executed. */ |
|||
#define REDIS_IN_CALLBACK 0x10 |
|||
|
|||
/* Flag that is set when the async context has one or more subscriptions. */ |
|||
#define REDIS_SUBSCRIBED 0x20 |
|||
|
|||
#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 { |
|||
int type; /* REDIS_REPLY_* */ |
|||
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ |
|||
int len; /* Length of string */ |
|||
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ |
|||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ |
|||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ |
|||
} redisReply; |
|||
|
|||
typedef struct redisReadTask { |
|||
int type; |
|||
int elements; /* number of elements in multibulk container */ |
|||
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 { |
|||
void *(*createString)(const redisReadTask*, char*, size_t); |
|||
void *(*createArray)(const redisReadTask*, int); |
|||
void *(*createInteger)(const redisReadTask*, long long); |
|||
void *(*createNil)(const redisReadTask*); |
|||
void (*freeObject)(void*); |
|||
} redisReplyObjectFunctions; |
|||
|
|||
/* State for the protocol parser */ |
|||
typedef struct redisReader { |
|||
int err; /* Error flags, 0 when there is no error */ |
|||
char errstr[128]; /* String representation of error when applicable */ |
|||
|
|||
char *buf; /* Read buffer */ |
|||
size_t pos; /* Buffer cursor */ |
|||
size_t len; /* Buffer length */ |
|||
|
|||
redisReadTask rstack[3]; |
|||
int ridx; /* Index of current read task */ |
|||
void *reply; /* Temporary reply pointer */ |
|||
|
|||
redisReplyObjectFunctions *fn; |
|||
void *privdata; |
|||
} redisReader; |
|||
|
|||
/* Public API for the protocol parser. */ |
|||
redisReader *redisReaderCreate(void); |
|||
void redisReaderFree(redisReader *r); |
|||
int redisReaderFeed(redisReader *r, const char *buf, size_t len); |
|||
int redisReaderGetReply(redisReader *r, void **reply); |
|||
|
|||
/* Backwards compatibility, can be removed on big version bump. */ |
|||
#define redisReplyReaderCreate redisReaderCreate |
|||
#define redisReplyReaderFree redisReaderFree |
|||
#define redisReplyReaderFeed redisReaderFeed |
|||
#define redisReplyReaderGetReply redisReaderGetReply |
|||
#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) |
|||
#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) |
|||
#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) |
|||
|
|||
/* Function to free the reply objects hiredis returns by default. */ |
|||
void freeReplyObject(void *reply); |
|||
|
|||
/* Functions to format a command according to the protocol. */ |
|||
int redisvFormatCommand(char **target, const char *format, va_list ap); |
|||
int redisFormatCommand(char **target, const char *format, ...); |
|||
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); |
|||
|
|||
/* Context for a connection to Redis */ |
|||
typedef struct redisContext { |
|||
int err; /* Error flags, 0 when there is no error */ |
|||
char errstr[128]; /* String representation of error when applicable */ |
|||
int fd; |
|||
int flags; |
|||
char *obuf; /* Write buffer */ |
|||
redisReader *reader; /* Protocol reader */ |
|||
} redisContext; |
|||
|
|||
redisContext *redisConnect(const char *ip, int port); |
|||
redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval tv); |
|||
redisContext *redisConnectNonBlock(const char *ip, int port); |
|||
redisContext *redisConnectUnix(const char *path); |
|||
redisContext *redisConnectUnixWithTimeout(const char *path, struct timeval tv); |
|||
redisContext *redisConnectUnixNonBlock(const char *path); |
|||
int redisSetTimeout(redisContext *c, struct timeval tv); |
|||
void redisFree(redisContext *c); |
|||
int redisBufferRead(redisContext *c); |
|||
int redisBufferWrite(redisContext *c, int *done); |
|||
|
|||
/* In a blocking context, this function first checks if there are unconsumed |
|||
* replies to return and returns one if so. Otherwise, it flushes the output |
|||
* buffer to the socket and reads until it has a reply. In a non-blocking |
|||
* context, it will return unconsumed replies until there are no more. */ |
|||
int redisGetReply(redisContext *c, void **reply); |
|||
int redisGetReplyFromReader(redisContext *c, void **reply); |
|||
|
|||
/* Write a command to the output buffer. Use these functions in blocking mode |
|||
* to get a pipeline of commands. */ |
|||
int redisvAppendCommand(redisContext *c, const char *format, va_list ap); |
|||
int redisAppendCommand(redisContext *c, const char *format, ...); |
|||
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); |
|||
|
|||
/* Issue a command to Redis. In a blocking context, it is identical to calling |
|||
* redisAppendCommand, followed by redisGetReply. The function will return |
|||
* NULL if there was an error in performing the request, otherwise it will |
|||
* return the reply. In a non-blocking context, it is identical to calling |
|||
* only redisAppendCommand and will always return NULL. */ |
|||
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 |
|||
@ -0,0 +1,256 @@ |
|||
/* Extracted from anet.c to work properly with Hiredis error reporting. |
|||
* |
|||
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
|||
* Copyright (c) 2010-2011, 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#include "fmacros.h" |
|||
#include <sys/types.h> |
|||
#include <sys/socket.h> |
|||
#include <sys/select.h> |
|||
#include <sys/un.h> |
|||
#include <netinet/in.h> |
|||
#include <netinet/tcp.h> |
|||
#include <arpa/inet.h> |
|||
#include <unistd.h> |
|||
#include <fcntl.h> |
|||
#include <string.h> |
|||
#include <netdb.h> |
|||
#include <errno.h> |
|||
#include <stdarg.h> |
|||
#include <stdio.h> |
|||
|
|||
#include "net.h" |
|||
#include "sds.h" |
|||
|
|||
/* Defined in hiredis.c */ |
|||
void __redisSetError(redisContext *c, int type, const char *str); |
|||
|
|||
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { |
|||
char buf[128]; |
|||
size_t len = 0; |
|||
|
|||
if (prefix != NULL) |
|||
len = snprintf(buf,sizeof(buf),"%s: ",prefix); |
|||
strerror_r(errno,buf+len,sizeof(buf)-len); |
|||
__redisSetError(c,type,buf); |
|||
} |
|||
|
|||
static int redisCreateSocket(redisContext *c, int type) { |
|||
int s, on = 1; |
|||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
|||
return REDIS_ERR; |
|||
} |
|||
if (type == AF_INET) { |
|||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
|||
close(s); |
|||
return REDIS_ERR; |
|||
} |
|||
} |
|||
return s; |
|||
} |
|||
|
|||
static int redisSetBlocking(redisContext *c, int fd, int blocking) { |
|||
int flags; |
|||
|
|||
/* Set the socket nonblocking. |
|||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be |
|||
* interrupted by a signal. */ |
|||
if ((flags = fcntl(fd, F_GETFL)) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
if (blocking) |
|||
flags &= ~O_NONBLOCK; |
|||
else |
|||
flags |= O_NONBLOCK; |
|||
|
|||
if (fcntl(fd, F_SETFL, flags) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
return REDIS_OK; |
|||
} |
|||
|
|||
static int redisSetTcpNoDelay(redisContext *c, int fd) { |
|||
int yes = 1; |
|||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
return REDIS_OK; |
|||
} |
|||
|
|||
static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) { |
|||
struct timeval to; |
|||
struct timeval *toptr = NULL; |
|||
fd_set wfd; |
|||
int err; |
|||
socklen_t errlen; |
|||
|
|||
/* Only use timeout when not NULL. */ |
|||
if (timeout != NULL) { |
|||
to = *timeout; |
|||
toptr = &to; |
|||
} |
|||
|
|||
if (errno == EINPROGRESS) { |
|||
FD_ZERO(&wfd); |
|||
FD_SET(fd, &wfd); |
|||
|
|||
if (select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"select(2)"); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
if (!FD_ISSET(fd, &wfd)) { |
|||
errno = ETIMEDOUT; |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
err = 0; |
|||
errlen = sizeof(err); |
|||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
if (err) { |
|||
errno = err; |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
return REDIS_OK; |
|||
} |
|||
|
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
|||
close(fd); |
|||
return REDIS_ERR; |
|||
} |
|||
|
|||
int redisContextSetTimeout(redisContext *c, struct timeval tv) { |
|||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); |
|||
return REDIS_ERR; |
|||
} |
|||
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { |
|||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); |
|||
return REDIS_ERR; |
|||
} |
|||
return REDIS_OK; |
|||
} |
|||
|
|||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) { |
|||
int s; |
|||
int blocking = (c->flags & REDIS_BLOCK); |
|||
struct sockaddr_in sa; |
|||
|
|||
if ((s = redisCreateSocket(c,AF_INET)) < 0) |
|||
return REDIS_ERR; |
|||
if (redisSetBlocking(c,s,0) != REDIS_OK) |
|||
return REDIS_ERR; |
|||
|
|||
sa.sin_family = AF_INET; |
|||
sa.sin_port = htons(port); |
|||
if (inet_aton(addr, &sa.sin_addr) == 0) { |
|||
struct hostent *he; |
|||
|
|||
he = gethostbyname(addr); |
|||
if (he == NULL) { |
|||
char buf[128]; |
|||
snprintf(buf,sizeof(buf),"Can't resolve: %s", addr); |
|||
__redisSetError(c,REDIS_ERR_OTHER,buf); |
|||
close(s); |
|||
return REDIS_ERR; |
|||
} |
|||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr)); |
|||
} |
|||
|
|||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { |
|||
if (errno == EINPROGRESS && !blocking) { |
|||
/* This is ok. */ |
|||
} else { |
|||
if (redisContextWaitReady(c,s,timeout) != REDIS_OK) |
|||
return REDIS_ERR; |
|||
} |
|||
} |
|||
|
|||
/* Reset socket to be blocking after connect(2). */ |
|||
if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) |
|||
return REDIS_ERR; |
|||
|
|||
if (redisSetTcpNoDelay(c,s) != REDIS_OK) |
|||
return REDIS_ERR; |
|||
|
|||
c->fd = s; |
|||
c->flags |= REDIS_CONNECTED; |
|||
return REDIS_OK; |
|||
} |
|||
|
|||
int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout) { |
|||
int s; |
|||
int blocking = (c->flags & REDIS_BLOCK); |
|||
struct sockaddr_un sa; |
|||
|
|||
if ((s = redisCreateSocket(c,AF_LOCAL)) < 0) |
|||
return REDIS_ERR; |
|||
if (redisSetBlocking(c,s,0) != REDIS_OK) |
|||
return REDIS_ERR; |
|||
|
|||
sa.sun_family = AF_LOCAL; |
|||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); |
|||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) { |
|||
if (errno == EINPROGRESS && !blocking) { |
|||
/* This is ok. */ |
|||
} else { |
|||
if (redisContextWaitReady(c,s,timeout) != REDIS_OK) |
|||
return REDIS_ERR; |
|||
} |
|||
} |
|||
|
|||
/* Reset socket to be blocking after connect(2). */ |
|||
if (blocking && redisSetBlocking(c,s,1) != REDIS_OK) |
|||
return REDIS_ERR; |
|||
|
|||
c->fd = s; |
|||
c->flags |= REDIS_CONNECTED; |
|||
return REDIS_OK; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/* Extracted from anet.c to work properly with Hiredis error reporting. |
|||
* |
|||
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
|||
* Copyright (c) 2010-2011, 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#ifndef __NET_H |
|||
#define __NET_H |
|||
|
|||
#include "hiredis.h" |
|||
|
|||
#if defined(__sun) |
|||
#define AF_LOCAL AF_UNIX |
|||
#endif |
|||
|
|||
int redisContextSetTimeout(redisContext *c, struct timeval tv); |
|||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout); |
|||
int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout); |
|||
|
|||
#endif |
|||
@ -0,0 +1,605 @@ |
|||
/* SDSLib, A C dynamic strings library |
|||
* |
|||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <ctype.h> |
|||
#include "sds.h" |
|||
|
|||
#ifdef SDS_ABORT_ON_OOM |
|||
static void sdsOomAbort(void) { |
|||
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n"); |
|||
abort(); |
|||
} |
|||
#endif |
|||
|
|||
sds sdsnewlen(const void *init, size_t initlen) { |
|||
struct sdshdr *sh; |
|||
|
|||
sh = malloc(sizeof(struct sdshdr)+initlen+1); |
|||
#ifdef SDS_ABORT_ON_OOM |
|||
if (sh == NULL) sdsOomAbort(); |
|||
#else |
|||
if (sh == NULL) return NULL; |
|||
#endif |
|||
sh->len = initlen; |
|||
sh->free = 0; |
|||
if (initlen) { |
|||
if (init) memcpy(sh->buf, init, initlen); |
|||
else memset(sh->buf,0,initlen); |
|||
} |
|||
sh->buf[initlen] = '\0'; |
|||
return (char*)sh->buf; |
|||
} |
|||
|
|||
sds sdsempty(void) { |
|||
return sdsnewlen("",0); |
|||
} |
|||
|
|||
sds sdsnew(const char *init) { |
|||
size_t initlen = (init == NULL) ? 0 : strlen(init); |
|||
return sdsnewlen(init, initlen); |
|||
} |
|||
|
|||
sds sdsdup(const sds s) { |
|||
return sdsnewlen(s, sdslen(s)); |
|||
} |
|||
|
|||
void sdsfree(sds s) { |
|||
if (s == NULL) return; |
|||
free(s-sizeof(struct sdshdr)); |
|||
} |
|||
|
|||
void sdsupdatelen(sds s) { |
|||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); |
|||
int reallen = strlen(s); |
|||
sh->free += (sh->len-reallen); |
|||
sh->len = reallen; |
|||
} |
|||
|
|||
static sds sdsMakeRoomFor(sds s, size_t addlen) { |
|||
struct sdshdr *sh, *newsh; |
|||
size_t free = sdsavail(s); |
|||
size_t len, newlen; |
|||
|
|||
if (free >= addlen) return s; |
|||
len = sdslen(s); |
|||
sh = (void*) (s-(sizeof(struct sdshdr))); |
|||
newlen = (len+addlen)*2; |
|||
newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1); |
|||
#ifdef SDS_ABORT_ON_OOM |
|||
if (newsh == NULL) sdsOomAbort(); |
|||
#else |
|||
if (newsh == NULL) return NULL; |
|||
#endif |
|||
|
|||
newsh->free = newlen - len; |
|||
return newsh->buf; |
|||
} |
|||
|
|||
/* Grow the sds to have the specified length. Bytes that were not part of |
|||
* the original length of the sds will be set to zero. */ |
|||
sds sdsgrowzero(sds s, size_t len) { |
|||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); |
|||
size_t totlen, curlen = sh->len; |
|||
|
|||
if (len <= curlen) return s; |
|||
s = sdsMakeRoomFor(s,len-curlen); |
|||
if (s == NULL) return NULL; |
|||
|
|||
/* Make sure added region doesn't contain garbage */ |
|||
sh = (void*)(s-(sizeof(struct sdshdr))); |
|||
memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ |
|||
totlen = sh->len+sh->free; |
|||
sh->len = len; |
|||
sh->free = totlen-sh->len; |
|||
return s; |
|||
} |
|||
|
|||
sds sdscatlen(sds s, const void *t, size_t len) { |
|||
struct sdshdr *sh; |
|||
size_t curlen = sdslen(s); |
|||
|
|||
s = sdsMakeRoomFor(s,len); |
|||
if (s == NULL) return NULL; |
|||
sh = (void*) (s-(sizeof(struct sdshdr))); |
|||
memcpy(s+curlen, t, len); |
|||
sh->len = curlen+len; |
|||
sh->free = sh->free-len; |
|||
s[curlen+len] = '\0'; |
|||
return s; |
|||
} |
|||
|
|||
sds sdscat(sds s, const char *t) { |
|||
return sdscatlen(s, t, strlen(t)); |
|||
} |
|||
|
|||
sds sdscpylen(sds s, char *t, size_t len) { |
|||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); |
|||
size_t totlen = sh->free+sh->len; |
|||
|
|||
if (totlen < len) { |
|||
s = sdsMakeRoomFor(s,len-sh->len); |
|||
if (s == NULL) return NULL; |
|||
sh = (void*) (s-(sizeof(struct sdshdr))); |
|||
totlen = sh->free+sh->len; |
|||
} |
|||
memcpy(s, t, len); |
|||
s[len] = '\0'; |
|||
sh->len = len; |
|||
sh->free = totlen-len; |
|||
return s; |
|||
} |
|||
|
|||
sds sdscpy(sds s, char *t) { |
|||
return sdscpylen(s, t, strlen(t)); |
|||
} |
|||
|
|||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) { |
|||
va_list cpy; |
|||
char *buf, *t; |
|||
size_t buflen = 16; |
|||
|
|||
while(1) { |
|||
buf = malloc(buflen); |
|||
#ifdef SDS_ABORT_ON_OOM |
|||
if (buf == NULL) sdsOomAbort(); |
|||
#else |
|||
if (buf == NULL) return NULL; |
|||
#endif |
|||
buf[buflen-2] = '\0'; |
|||
va_copy(cpy,ap); |
|||
vsnprintf(buf, buflen, fmt, cpy); |
|||
if (buf[buflen-2] != '\0') { |
|||
free(buf); |
|||
buflen *= 2; |
|||
continue; |
|||
} |
|||
break; |
|||
} |
|||
t = sdscat(s, buf); |
|||
free(buf); |
|||
return t; |
|||
} |
|||
|
|||
sds sdscatprintf(sds s, const char *fmt, ...) { |
|||
va_list ap; |
|||
char *t; |
|||
va_start(ap, fmt); |
|||
t = sdscatvprintf(s,fmt,ap); |
|||
va_end(ap); |
|||
return t; |
|||
} |
|||
|
|||
sds sdstrim(sds s, const char *cset) { |
|||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); |
|||
char *start, *end, *sp, *ep; |
|||
size_t len; |
|||
|
|||
sp = start = s; |
|||
ep = end = s+sdslen(s)-1; |
|||
while(sp <= end && strchr(cset, *sp)) sp++; |
|||
while(ep > start && strchr(cset, *ep)) ep--; |
|||
len = (sp > ep) ? 0 : ((ep-sp)+1); |
|||
if (sh->buf != sp) memmove(sh->buf, sp, len); |
|||
sh->buf[len] = '\0'; |
|||
sh->free = sh->free+(sh->len-len); |
|||
sh->len = len; |
|||
return s; |
|||
} |
|||
|
|||
sds sdsrange(sds s, int start, int end) { |
|||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); |
|||
size_t newlen, len = sdslen(s); |
|||
|
|||
if (len == 0) return s; |
|||
if (start < 0) { |
|||
start = len+start; |
|||
if (start < 0) start = 0; |
|||
} |
|||
if (end < 0) { |
|||
end = len+end; |
|||
if (end < 0) end = 0; |
|||
} |
|||
newlen = (start > end) ? 0 : (end-start)+1; |
|||
if (newlen != 0) { |
|||
if (start >= (signed)len) { |
|||
newlen = 0; |
|||
} else if (end >= (signed)len) { |
|||
end = len-1; |
|||
newlen = (start > end) ? 0 : (end-start)+1; |
|||
} |
|||
} else { |
|||
start = 0; |
|||
} |
|||
if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); |
|||
sh->buf[newlen] = 0; |
|||
sh->free = sh->free+(sh->len-newlen); |
|||
sh->len = newlen; |
|||
return s; |
|||
} |
|||
|
|||
void sdstolower(sds s) { |
|||
int len = sdslen(s), j; |
|||
|
|||
for (j = 0; j < len; j++) s[j] = tolower(s[j]); |
|||
} |
|||
|
|||
void sdstoupper(sds s) { |
|||
int len = sdslen(s), j; |
|||
|
|||
for (j = 0; j < len; j++) s[j] = toupper(s[j]); |
|||
} |
|||
|
|||
int sdscmp(sds s1, sds s2) { |
|||
size_t l1, l2, minlen; |
|||
int cmp; |
|||
|
|||
l1 = sdslen(s1); |
|||
l2 = sdslen(s2); |
|||
minlen = (l1 < l2) ? l1 : l2; |
|||
cmp = memcmp(s1,s2,minlen); |
|||
if (cmp == 0) return l1-l2; |
|||
return cmp; |
|||
} |
|||
|
|||
/* Split 's' with separator in 'sep'. An array |
|||
* of sds strings is returned. *count will be set |
|||
* by reference to the number of tokens returned. |
|||
* |
|||
* On out of memory, zero length string, zero length |
|||
* separator, NULL is returned. |
|||
* |
|||
* Note that 'sep' is able to split a string using |
|||
* a multi-character separator. For example |
|||
* sdssplit("foo_-_bar","_-_"); will return two |
|||
* elements "foo" and "bar". |
|||
* |
|||
* This version of the function is binary-safe but |
|||
* requires length arguments. sdssplit() is just the |
|||
* same function but for zero-terminated strings. |
|||
*/ |
|||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) { |
|||
int elements = 0, slots = 5, start = 0, j; |
|||
|
|||
sds *tokens = malloc(sizeof(sds)*slots); |
|||
#ifdef SDS_ABORT_ON_OOM |
|||
if (tokens == NULL) sdsOomAbort(); |
|||
#endif |
|||
if (seplen < 1 || len < 0 || tokens == NULL) return NULL; |
|||
if (len == 0) { |
|||
*count = 0; |
|||
return tokens; |
|||
} |
|||
for (j = 0; j < (len-(seplen-1)); j++) { |
|||
/* make sure there is room for the next element and the final one */ |
|||
if (slots < elements+2) { |
|||
sds *newtokens; |
|||
|
|||
slots *= 2; |
|||
newtokens = realloc(tokens,sizeof(sds)*slots); |
|||
if (newtokens == NULL) { |
|||
#ifdef SDS_ABORT_ON_OOM |
|||
sdsOomAbort(); |
|||
#else |
|||
goto cleanup; |
|||
#endif |
|||
} |
|||
tokens = newtokens; |
|||
} |
|||
/* search the separator */ |
|||
if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { |
|||
tokens[elements] = sdsnewlen(s+start,j-start); |
|||
if (tokens[elements] == NULL) { |
|||
#ifdef SDS_ABORT_ON_OOM |
|||
sdsOomAbort(); |
|||
#else |
|||
goto cleanup; |
|||
#endif |
|||
} |
|||
elements++; |
|||
start = j+seplen; |
|||
j = j+seplen-1; /* skip the separator */ |
|||
} |
|||
} |
|||
/* Add the final element. We are sure there is room in the tokens array. */ |
|||
tokens[elements] = sdsnewlen(s+start,len-start); |
|||
if (tokens[elements] == NULL) { |
|||
#ifdef SDS_ABORT_ON_OOM |
|||
sdsOomAbort(); |
|||
#else |
|||
goto cleanup; |
|||
#endif |
|||
} |
|||
elements++; |
|||
*count = elements; |
|||
return tokens; |
|||
|
|||
#ifndef SDS_ABORT_ON_OOM |
|||
cleanup: |
|||
{ |
|||
int i; |
|||
for (i = 0; i < elements; i++) sdsfree(tokens[i]); |
|||
free(tokens); |
|||
return NULL; |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
void sdsfreesplitres(sds *tokens, int count) { |
|||
if (!tokens) return; |
|||
while(count--) |
|||
sdsfree(tokens[count]); |
|||
free(tokens); |
|||
} |
|||
|
|||
sds sdsfromlonglong(long long value) { |
|||
char buf[32], *p; |
|||
unsigned long long v; |
|||
|
|||
v = (value < 0) ? -value : value; |
|||
p = buf+31; /* point to the last character */ |
|||
do { |
|||
*p-- = '0'+(v%10); |
|||
v /= 10; |
|||
} while(v); |
|||
if (value < 0) *p-- = '-'; |
|||
p++; |
|||
return sdsnewlen(p,32-(p-buf)); |
|||
} |
|||
|
|||
sds sdscatrepr(sds s, char *p, size_t len) { |
|||
s = sdscatlen(s,"\"",1); |
|||
if (s == NULL) return NULL; |
|||
|
|||
while(len--) { |
|||
switch(*p) { |
|||
case '\\': |
|||
case '"': |
|||
s = sdscatprintf(s,"\\%c",*p); |
|||
break; |
|||
case '\n': s = sdscatlen(s,"\\n",2); break; |
|||
case '\r': s = sdscatlen(s,"\\r",2); break; |
|||
case '\t': s = sdscatlen(s,"\\t",2); break; |
|||
case '\a': s = sdscatlen(s,"\\a",2); break; |
|||
case '\b': s = sdscatlen(s,"\\b",2); break; |
|||
default: |
|||
if (isprint(*p)) |
|||
s = sdscatprintf(s,"%c",*p); |
|||
else |
|||
s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); |
|||
break; |
|||
} |
|||
p++; |
|||
if (s == NULL) return NULL; |
|||
} |
|||
return sdscatlen(s,"\"",1); |
|||
} |
|||
|
|||
/* Split a line into arguments, where every argument can be in the |
|||
* following programming-language REPL-alike form: |
|||
* |
|||
* foo bar "newline are supported\n" and "\xff\x00otherstuff" |
|||
* |
|||
* The number of arguments is stored into *argc, and an array |
|||
* of sds is returned. The caller should sdsfree() all the returned |
|||
* strings and finally free() the array itself. |
|||
* |
|||
* Note that sdscatrepr() is able to convert back a string into |
|||
* a quoted string in the same format sdssplitargs() is able to parse. |
|||
*/ |
|||
sds *sdssplitargs(char *line, int *argc) { |
|||
char *p = line; |
|||
char *current = NULL; |
|||
char **vector = NULL, **_vector = NULL; |
|||
|
|||
*argc = 0; |
|||
while(1) { |
|||
/* skip blanks */ |
|||
while(*p && isspace(*p)) p++; |
|||
if (*p) { |
|||
/* get a token */ |
|||
int inq=0; /* set to 1 if we are in "quotes" */ |
|||
int done=0; |
|||
|
|||
if (current == NULL) { |
|||
current = sdsempty(); |
|||
if (current == NULL) goto err; |
|||
} |
|||
|
|||
while(!done) { |
|||
if (inq) { |
|||
if (*p == '\\' && *(p+1)) { |
|||
char c; |
|||
|
|||
p++; |
|||
switch(*p) { |
|||
case 'n': c = '\n'; break; |
|||
case 'r': c = '\r'; break; |
|||
case 't': c = '\t'; break; |
|||
case 'b': c = '\b'; break; |
|||
case 'a': c = '\a'; break; |
|||
default: c = *p; break; |
|||
} |
|||
current = sdscatlen(current,&c,1); |
|||
} else if (*p == '"') { |
|||
/* closing quote must be followed by a space */ |
|||
if (*(p+1) && !isspace(*(p+1))) goto err; |
|||
done=1; |
|||
} else if (!*p) { |
|||
/* unterminated quotes */ |
|||
goto err; |
|||
} else { |
|||
current = sdscatlen(current,p,1); |
|||
} |
|||
} else { |
|||
switch(*p) { |
|||
case ' ': |
|||
case '\n': |
|||
case '\r': |
|||
case '\t': |
|||
case '\0': |
|||
done=1; |
|||
break; |
|||
case '"': |
|||
inq=1; |
|||
break; |
|||
default: |
|||
current = sdscatlen(current,p,1); |
|||
break; |
|||
} |
|||
} |
|||
if (*p) p++; |
|||
if (current == NULL) goto err; |
|||
} |
|||
/* add the token to the vector */ |
|||
_vector = realloc(vector,((*argc)+1)*sizeof(char*)); |
|||
if (_vector == NULL) goto err; |
|||
|
|||
vector = _vector; |
|||
vector[*argc] = current; |
|||
(*argc)++; |
|||
current = NULL; |
|||
} else { |
|||
return vector; |
|||
} |
|||
} |
|||
|
|||
err: |
|||
while((*argc)--) |
|||
sdsfree(vector[*argc]); |
|||
if (vector != NULL) free(vector); |
|||
if (current != NULL) sdsfree(current); |
|||
return NULL; |
|||
} |
|||
|
|||
#ifdef SDS_TEST_MAIN |
|||
#include <stdio.h> |
|||
|
|||
int __failed_tests = 0; |
|||
int __test_num = 0; |
|||
#define test_cond(descr,_c) do { \ |
|||
__test_num++; printf("%d - %s: ", __test_num, descr); \ |
|||
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \ |
|||
} while(0); |
|||
#define test_report() do { \ |
|||
printf("%d tests, %d passed, %d failed\n", __test_num, \ |
|||
__test_num-__failed_tests, __failed_tests); \ |
|||
if (__failed_tests) { \ |
|||
printf("=== WARNING === We have failed tests here...\n"); \ |
|||
} \ |
|||
} while(0); |
|||
|
|||
int main(void) { |
|||
{ |
|||
sds x = sdsnew("foo"), y; |
|||
|
|||
test_cond("Create a string and obtain the length", |
|||
sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) |
|||
|
|||
sdsfree(x); |
|||
x = sdsnewlen("foo",2); |
|||
test_cond("Create a string with specified length", |
|||
sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) |
|||
|
|||
x = sdscat(x,"bar"); |
|||
test_cond("Strings concatenation", |
|||
sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); |
|||
|
|||
x = sdscpy(x,"a"); |
|||
test_cond("sdscpy() against an originally longer string", |
|||
sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) |
|||
|
|||
x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); |
|||
test_cond("sdscpy() against an originally shorter string", |
|||
sdslen(x) == 33 && |
|||
memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) |
|||
|
|||
sdsfree(x); |
|||
x = sdscatprintf(sdsempty(),"%d",123); |
|||
test_cond("sdscatprintf() seems working in the base case", |
|||
sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) |
|||
|
|||
sdsfree(x); |
|||
x = sdstrim(sdsnew("xxciaoyyy"),"xy"); |
|||
test_cond("sdstrim() correctly trims characters", |
|||
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) |
|||
|
|||
y = sdsrange(sdsdup(x),1,1); |
|||
test_cond("sdsrange(...,1,1)", |
|||
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) |
|||
|
|||
sdsfree(y); |
|||
y = sdsrange(sdsdup(x),1,-1); |
|||
test_cond("sdsrange(...,1,-1)", |
|||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) |
|||
|
|||
sdsfree(y); |
|||
y = sdsrange(sdsdup(x),-2,-1); |
|||
test_cond("sdsrange(...,-2,-1)", |
|||
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) |
|||
|
|||
sdsfree(y); |
|||
y = sdsrange(sdsdup(x),2,1); |
|||
test_cond("sdsrange(...,2,1)", |
|||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0) |
|||
|
|||
sdsfree(y); |
|||
y = sdsrange(sdsdup(x),1,100); |
|||
test_cond("sdsrange(...,1,100)", |
|||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) |
|||
|
|||
sdsfree(y); |
|||
y = sdsrange(sdsdup(x),100,100); |
|||
test_cond("sdsrange(...,100,100)", |
|||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0) |
|||
|
|||
sdsfree(y); |
|||
sdsfree(x); |
|||
x = sdsnew("foo"); |
|||
y = sdsnew("foa"); |
|||
test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) |
|||
|
|||
sdsfree(y); |
|||
sdsfree(x); |
|||
x = sdsnew("bar"); |
|||
y = sdsnew("bar"); |
|||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) |
|||
|
|||
sdsfree(y); |
|||
sdsfree(x); |
|||
x = sdsnew("aar"); |
|||
y = sdsnew("bar"); |
|||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) |
|||
} |
|||
test_report() |
|||
} |
|||
#endif |
|||
@ -0,0 +1,88 @@ |
|||
/* SDSLib, A C dynamic strings library |
|||
* |
|||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez 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: |
|||
* |
|||
* * Redistributions of source code must retain the above copyright notice, |
|||
* this list of conditions and the following disclaimer. |
|||
* * Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* * Neither the name of Redis nor the names of its contributors may be used |
|||
* to endorse or promote products derived from this software without |
|||
* specific prior written permission. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
* POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
|
|||
#ifndef __SDS_H |
|||
#define __SDS_H |
|||
|
|||
#include <sys/types.h> |
|||
#include <stdarg.h> |
|||
|
|||
typedef char *sds; |
|||
|
|||
struct sdshdr { |
|||
int len; |
|||
int free; |
|||
char buf[]; |
|||
}; |
|||
|
|||
static inline size_t sdslen(const sds s) { |
|||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); |
|||
return sh->len; |
|||
} |
|||
|
|||
static inline size_t sdsavail(const sds s) { |
|||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); |
|||
return sh->free; |
|||
} |
|||
|
|||
sds sdsnewlen(const void *init, size_t initlen); |
|||
sds sdsnew(const char *init); |
|||
sds sdsempty(void); |
|||
size_t sdslen(const sds s); |
|||
sds sdsdup(const sds s); |
|||
void sdsfree(sds s); |
|||
size_t sdsavail(sds s); |
|||
sds sdsgrowzero(sds s, size_t len); |
|||
sds sdscatlen(sds s, const void *t, size_t len); |
|||
sds sdscat(sds s, const char *t); |
|||
sds sdscpylen(sds s, char *t, size_t len); |
|||
sds sdscpy(sds s, char *t); |
|||
|
|||
sds sdscatvprintf(sds s, const char *fmt, va_list ap); |
|||
#ifdef __GNUC__ |
|||
sds sdscatprintf(sds s, const char *fmt, ...) |
|||
__attribute__((format(printf, 2, 3))); |
|||
#else |
|||
sds sdscatprintf(sds s, const char *fmt, ...); |
|||
#endif |
|||
|
|||
sds sdstrim(sds s, const char *cset); |
|||
sds sdsrange(sds s, int start, int end); |
|||
void sdsupdatelen(sds s); |
|||
int sdscmp(sds s1, sds s2); |
|||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count); |
|||
void sdsfreesplitres(sds *tokens, int count); |
|||
void sdstolower(sds s); |
|||
void sdstoupper(sds s); |
|||
sds sdsfromlonglong(long long value); |
|||
sds sdscatrepr(sds s, char *p, size_t len); |
|||
sds *sdssplitargs(char *line, int *argc); |
|||
|
|||
#endif |
|||
@ -0,0 +1,638 @@ |
|||
#include "fmacros.h" |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <strings.h> |
|||
#include <sys/time.h> |
|||
#include <assert.h> |
|||
#include <unistd.h> |
|||
#include <signal.h> |
|||
#include <errno.h> |
|||
|
|||
#include "hiredis.h" |
|||
|
|||
enum connection_type { |
|||
CONN_TCP, |
|||
CONN_UNIX |
|||
}; |
|||
|
|||
struct config { |
|||
enum connection_type type; |
|||
|
|||
struct { |
|||
const char *host; |
|||
int port; |
|||
} tcp; |
|||
|
|||
struct { |
|||
const char *path; |
|||
} unix; |
|||
}; |
|||
|
|||
/* The following lines make up our testing "framework" :) */ |
|||
static int tests = 0, fails = 0; |
|||
#define test(_s) { printf("#%02d ", ++tests); printf(_s); } |
|||
#define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;} |
|||
|
|||
static long long usec(void) { |
|||
struct timeval tv; |
|||
gettimeofday(&tv,NULL); |
|||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; |
|||
} |
|||
|
|||
static redisContext *select_database(redisContext *c) { |
|||
redisReply *reply; |
|||
|
|||
/* Switch to DB 9 for testing, now that we know we can chat. */ |
|||
reply = redisCommand(c,"SELECT 9"); |
|||
assert(reply != NULL); |
|||
freeReplyObject(reply); |
|||
|
|||
/* Make sure the DB is emtpy */ |
|||
reply = redisCommand(c,"DBSIZE"); |
|||
assert(reply != NULL); |
|||
if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { |
|||
/* Awesome, DB 9 is empty and we can continue. */ |
|||
freeReplyObject(reply); |
|||
} else { |
|||
printf("Database #9 is not empty, test can not continue\n"); |
|||
exit(1); |
|||
} |
|||
|
|||
return c; |
|||
} |
|||
|
|||
static void disconnect(redisContext *c) { |
|||
redisReply *reply; |
|||
|
|||
/* Make sure we're on DB 9. */ |
|||
reply = redisCommand(c,"SELECT 9"); |
|||
assert(reply != NULL); |
|||
freeReplyObject(reply); |
|||
reply = redisCommand(c,"FLUSHDB"); |
|||
assert(reply != NULL); |
|||
freeReplyObject(reply); |
|||
|
|||
/* Free the context as well. */ |
|||
redisFree(c); |
|||
} |
|||
|
|||
static redisContext *connect(struct config config) { |
|||
redisContext *c = NULL; |
|||
|
|||
if (config.type == CONN_TCP) { |
|||
c = redisConnect(config.tcp.host, config.tcp.port); |
|||
} else if (config.type == CONN_UNIX) { |
|||
c = redisConnectUnix(config.unix.path); |
|||
} else { |
|||
assert(NULL); |
|||
} |
|||
|
|||
if (c->err) { |
|||
printf("Connection error: %s\n", c->errstr); |
|||
exit(1); |
|||
} |
|||
|
|||
return select_database(c); |
|||
} |
|||
|
|||
static void test_format_commands(void) { |
|||
char *cmd; |
|||
int len; |
|||
|
|||
test("Format command without interpolation: "); |
|||
len = redisFormatCommand(&cmd,"SET foo bar"); |
|||
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)); |
|||
free(cmd); |
|||
|
|||
test("Format command with %%s string interpolation: "); |
|||
len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); |
|||
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)); |
|||
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 an empty string in between proper interpolations: "); |
|||
len = redisFormatCommand(&cmd,"SET %s %s","","foo"); |
|||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && |
|||
len == 4+4+(3+2)+4+(0+2)+4+(3+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\0xxx"; |
|||
argv[2] = "bar"; |
|||
size_t lens[3] = { 3, 7, 3 }; |
|||
int argc = 3; |
|||
|
|||
test("Format command by passing argc/argv without lengths: "); |
|||
len = redisFormatCommandArgv(&cmd,argc,argv,NULL); |
|||
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)); |
|||
free(cmd); |
|||
|
|||
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$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_reply_reader(void) { |
|||
redisReader *reader; |
|||
void *reply; |
|||
int ret; |
|||
|
|||
test("Error handling in reply parser: "); |
|||
reader = redisReaderCreate(); |
|||
redisReaderFeed(reader,(char*)"@foo\r\n",6); |
|||
ret = redisReaderGetReply(reader,NULL); |
|||
test_cond(ret == REDIS_ERR && |
|||
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); |
|||
redisReaderFree(reader); |
|||
|
|||
/* when the reply already contains multiple items, they must be free'd |
|||
* on an error. valgrind will bark when this doesn't happen. */ |
|||
test("Memory cleanup in reply parser: "); |
|||
reader = redisReaderCreate(); |
|||
redisReaderFeed(reader,(char*)"*2\r\n",4); |
|||
redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); |
|||
redisReaderFeed(reader,(char*)"@foo\r\n",6); |
|||
ret = redisReaderGetReply(reader,NULL); |
|||
test_cond(ret == REDIS_ERR && |
|||
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); |
|||
redisReaderFree(reader); |
|||
|
|||
test("Set error on nested multi bulks with depth > 1: "); |
|||
reader = redisReaderCreate(); |
|||
redisReaderFeed(reader,(char*)"*1\r\n",4); |
|||
redisReaderFeed(reader,(char*)"*1\r\n",4); |
|||
redisReaderFeed(reader,(char*)"*1\r\n",4); |
|||
ret = redisReaderGetReply(reader,NULL); |
|||
test_cond(ret == REDIS_ERR && |
|||
strncasecmp(reader->errstr,"No support for",14) == 0); |
|||
redisReaderFree(reader); |
|||
|
|||
test("Works with NULL functions for reply: "); |
|||
reader = redisReaderCreate(); |
|||
reader->fn = NULL; |
|||
redisReaderFeed(reader,(char*)"+OK\r\n",5); |
|||
ret = redisReaderGetReply(reader,&reply); |
|||
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); |
|||
redisReaderFree(reader); |
|||
|
|||
test("Works when a single newline (\\r\\n) covers two calls to feed: "); |
|||
reader = redisReaderCreate(); |
|||
reader->fn = NULL; |
|||
redisReaderFeed(reader,(char*)"+OK\r",4); |
|||
ret = redisReaderGetReply(reader,&reply); |
|||
assert(ret == REDIS_OK && reply == NULL); |
|||
redisReaderFeed(reader,(char*)"\n",1); |
|||
ret = redisReaderGetReply(reader,&reply); |
|||
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); |
|||
redisReaderFree(reader); |
|||
|
|||
test("Don't reset state after protocol error: "); |
|||
reader = redisReaderCreate(); |
|||
reader->fn = NULL; |
|||
redisReaderFeed(reader,(char*)"x",1); |
|||
ret = redisReaderGetReply(reader,&reply); |
|||
assert(ret == REDIS_ERR); |
|||
ret = redisReaderGetReply(reader,&reply); |
|||
test_cond(ret == REDIS_ERR && reply == NULL); |
|||
redisReaderFree(reader); |
|||
|
|||
/* Regression test for issue #45 on GitHub. */ |
|||
test("Don't do empty allocation for empty multi bulk: "); |
|||
reader = redisReaderCreate(); |
|||
redisReaderFeed(reader,(char*)"*0\r\n",4); |
|||
ret = redisReaderGetReply(reader,&reply); |
|||
test_cond(ret == REDIS_OK && |
|||
((redisReply*)reply)->type == REDIS_REPLY_ARRAY && |
|||
((redisReply*)reply)->elements == 0); |
|||
freeReplyObject(reply); |
|||
redisReaderFree(reader); |
|||
} |
|||
|
|||
static void test_blocking_connection_errors(void) { |
|||
redisContext *c; |
|||
|
|||
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); |
|||
|
|||
test("Returns error when the port is not open: "); |
|||
c = redisConnect((char*)"localhost", 1); |
|||
test_cond(c->err == REDIS_ERR_IO && |
|||
strcmp(c->errstr,"Connection refused") == 0); |
|||
redisFree(c); |
|||
|
|||
test("Returns error when the unix socket path doesn't accept connections: "); |
|||
c = redisConnectUnix((char*)"/tmp/idontexist.sock"); |
|||
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ |
|||
redisFree(c); |
|||
} |
|||
|
|||
static void test_blocking_connection(struct config config) { |
|||
redisContext *c; |
|||
redisReply *reply; |
|||
|
|||
c = connect(config); |
|||
|
|||
test("Is able to deliver commands: "); |
|||
reply = redisCommand(c,"PING"); |
|||
test_cond(reply->type == REDIS_REPLY_STATUS && |
|||
strcasecmp(reply->str,"pong") == 0) |
|||
freeReplyObject(reply); |
|||
|
|||
test("Is a able to send commands verbatim: "); |
|||
reply = redisCommand(c,"SET foo bar"); |
|||
test_cond (reply->type == REDIS_REPLY_STATUS && |
|||
strcasecmp(reply->str,"ok") == 0) |
|||
freeReplyObject(reply); |
|||
|
|||
test("%%s String interpolation works: "); |
|||
reply = redisCommand(c,"SET %s %s","foo","hello world"); |
|||
freeReplyObject(reply); |
|||
reply = redisCommand(c,"GET foo"); |
|||
test_cond(reply->type == REDIS_REPLY_STRING && |
|||
strcmp(reply->str,"hello world") == 0); |
|||
freeReplyObject(reply); |
|||
|
|||
test("%%b String interpolation works: "); |
|||
reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11); |
|||
freeReplyObject(reply); |
|||
reply = redisCommand(c,"GET foo"); |
|||
test_cond(reply->type == REDIS_REPLY_STRING && |
|||
memcmp(reply->str,"hello\x00world",11) == 0) |
|||
|
|||
test("Binary reply length is correct: "); |
|||
test_cond(reply->len == 11) |
|||
freeReplyObject(reply); |
|||
|
|||
test("Can parse nil replies: "); |
|||
reply = redisCommand(c,"GET nokey"); |
|||
test_cond(reply->type == REDIS_REPLY_NIL) |
|||
freeReplyObject(reply); |
|||
|
|||
/* test 7 */ |
|||
test("Can parse integer replies: "); |
|||
reply = redisCommand(c,"INCR mycounter"); |
|||
test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) |
|||
freeReplyObject(reply); |
|||
|
|||
test("Can parse multi bulk replies: "); |
|||
freeReplyObject(redisCommand(c,"LPUSH mylist foo")); |
|||
freeReplyObject(redisCommand(c,"LPUSH mylist bar")); |
|||
reply = redisCommand(c,"LRANGE mylist 0 -1"); |
|||
test_cond(reply->type == REDIS_REPLY_ARRAY && |
|||
reply->elements == 2 && |
|||
!memcmp(reply->element[0]->str,"bar",3) && |
|||
!memcmp(reply->element[1]->str,"foo",3)) |
|||
freeReplyObject(reply); |
|||
|
|||
/* m/e with multi bulk reply *before* other reply. |
|||
* specifically test ordering of reply items to parse. */ |
|||
test("Can handle nested multi bulk replies: "); |
|||
freeReplyObject(redisCommand(c,"MULTI")); |
|||
freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); |
|||
freeReplyObject(redisCommand(c,"PING")); |
|||
reply = (redisCommand(c,"EXEC")); |
|||
test_cond(reply->type == REDIS_REPLY_ARRAY && |
|||
reply->elements == 2 && |
|||
reply->element[0]->type == REDIS_REPLY_ARRAY && |
|||
reply->element[0]->elements == 2 && |
|||
!memcmp(reply->element[0]->element[0]->str,"bar",3) && |
|||
!memcmp(reply->element[0]->element[1]->str,"foo",3) && |
|||
reply->element[1]->type == REDIS_REPLY_STATUS && |
|||
strcasecmp(reply->element[1]->str,"pong") == 0); |
|||
freeReplyObject(reply); |
|||
|
|||
disconnect(c); |
|||
} |
|||
|
|||
static void test_blocking_io_errors(struct config config) { |
|||
redisContext *c; |
|||
redisReply *reply; |
|||
void *_reply; |
|||
int major, minor; |
|||
|
|||
/* Connect to target given by config. */ |
|||
c = connect(config); |
|||
{ |
|||
/* 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,&_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); |
|||
redisFree(c); |
|||
|
|||
c = connect(config); |
|||
test("Returns I/O error on socket timeout: "); |
|||
struct timeval tv = { 0, 1000 }; |
|||
assert(redisSetTimeout(c,tv) == REDIS_OK); |
|||
test_cond(redisGetReply(c,&_reply) == REDIS_ERR && |
|||
c->err == REDIS_ERR_IO && errno == EAGAIN); |
|||
redisFree(c); |
|||
} |
|||
|
|||
static void test_throughput(struct config config) { |
|||
redisContext *c = connect(config); |
|||
redisReply **replies; |
|||
int i, num; |
|||
long long t1, t2; |
|||
|
|||
test("Throughput:\n"); |
|||
for (i = 0; i < 500; i++) |
|||
freeReplyObject(redisCommand(c,"LPUSH mylist foo")); |
|||
|
|||
num = 1000; |
|||
replies = malloc(sizeof(redisReply*)*num); |
|||
t1 = usec(); |
|||
for (i = 0; i < num; i++) { |
|||
replies[i] = redisCommand(c,"PING"); |
|||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); |
|||
} |
|||
t2 = usec(); |
|||
for (i = 0; i < num; i++) freeReplyObject(replies[i]); |
|||
free(replies); |
|||
printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); |
|||
|
|||
replies = malloc(sizeof(redisReply*)*num); |
|||
t1 = usec(); |
|||
for (i = 0; i < num; i++) { |
|||
replies[i] = redisCommand(c,"LRANGE mylist 0 499"); |
|||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); |
|||
assert(replies[i] != NULL && replies[i]->elements == 500); |
|||
} |
|||
t2 = usec(); |
|||
for (i = 0; i < num; i++) freeReplyObject(replies[i]); |
|||
free(replies); |
|||
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); |
|||
|
|||
num = 10000; |
|||
replies = malloc(sizeof(redisReply*)*num); |
|||
for (i = 0; i < num; i++) |
|||
redisAppendCommand(c,"PING"); |
|||
t1 = usec(); |
|||
for (i = 0; i < num; i++) { |
|||
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); |
|||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); |
|||
} |
|||
t2 = usec(); |
|||
for (i = 0; i < num; i++) freeReplyObject(replies[i]); |
|||
free(replies); |
|||
printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); |
|||
|
|||
replies = malloc(sizeof(redisReply*)*num); |
|||
for (i = 0; i < num; i++) |
|||
redisAppendCommand(c,"LRANGE mylist 0 499"); |
|||
t1 = usec(); |
|||
for (i = 0; i < num; i++) { |
|||
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); |
|||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); |
|||
assert(replies[i] != NULL && replies[i]->elements == 500); |
|||
} |
|||
t2 = usec(); |
|||
for (i = 0; i < num; i++) freeReplyObject(replies[i]); |
|||
free(replies); |
|||
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); |
|||
|
|||
disconnect(c); |
|||
} |
|||
|
|||
// static long __test_callback_flags = 0; |
|||
// static void __test_callback(redisContext *c, void *privdata) { |
|||
// ((void)c); |
|||
// /* Shift to detect execution order */ |
|||
// __test_callback_flags <<= 8; |
|||
// __test_callback_flags |= (long)privdata; |
|||
// } |
|||
// |
|||
// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { |
|||
// ((void)c); |
|||
// /* Shift to detect execution order */ |
|||
// __test_callback_flags <<= 8; |
|||
// __test_callback_flags |= (long)privdata; |
|||
// if (reply) freeReplyObject(reply); |
|||
// } |
|||
// |
|||
// static redisContext *__connect_nonblock() { |
|||
// /* Reset callback flags */ |
|||
// __test_callback_flags = 0; |
|||
// return redisConnectNonBlock("127.0.0.1", port, NULL); |
|||
// } |
|||
// |
|||
// static void test_nonblocking_connection() { |
|||
// redisContext *c; |
|||
// int wdone = 0; |
|||
// |
|||
// test("Calls command callback when command is issued: "); |
|||
// c = __connect_nonblock(); |
|||
// redisSetCommandCallback(c,__test_callback,(void*)1); |
|||
// redisCommand(c,"PING"); |
|||
// test_cond(__test_callback_flags == 1); |
|||
// redisFree(c); |
|||
// |
|||
// test("Calls disconnect callback on redisDisconnect: "); |
|||
// c = __connect_nonblock(); |
|||
// redisSetDisconnectCallback(c,__test_callback,(void*)2); |
|||
// redisDisconnect(c); |
|||
// test_cond(__test_callback_flags == 2); |
|||
// redisFree(c); |
|||
// |
|||
// test("Calls disconnect callback and free callback on redisFree: "); |
|||
// c = __connect_nonblock(); |
|||
// redisSetDisconnectCallback(c,__test_callback,(void*)2); |
|||
// redisSetFreeCallback(c,__test_callback,(void*)4); |
|||
// redisFree(c); |
|||
// test_cond(__test_callback_flags == ((2 << 8) | 4)); |
|||
// |
|||
// test("redisBufferWrite against empty write buffer: "); |
|||
// c = __connect_nonblock(); |
|||
// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); |
|||
// redisFree(c); |
|||
// |
|||
// test("redisBufferWrite against not yet connected fd: "); |
|||
// c = __connect_nonblock(); |
|||
// redisCommand(c,"PING"); |
|||
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && |
|||
// strncmp(c->error,"write:",6) == 0); |
|||
// redisFree(c); |
|||
// |
|||
// test("redisBufferWrite against closed fd: "); |
|||
// c = __connect_nonblock(); |
|||
// redisCommand(c,"PING"); |
|||
// redisDisconnect(c); |
|||
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && |
|||
// strncmp(c->error,"write:",6) == 0); |
|||
// redisFree(c); |
|||
// |
|||
// test("Process callbacks in the right sequence: "); |
|||
// c = __connect_nonblock(); |
|||
// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); |
|||
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); |
|||
// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); |
|||
// |
|||
// /* Write output buffer */ |
|||
// wdone = 0; |
|||
// while(!wdone) { |
|||
// usleep(500); |
|||
// redisBufferWrite(c,&wdone); |
|||
// } |
|||
// |
|||
// /* Read until at least one callback is executed (the 3 replies will |
|||
// * arrive in a single packet, causing all callbacks to be executed in |
|||
// * a single pass). */ |
|||
// while(__test_callback_flags == 0) { |
|||
// assert(redisBufferRead(c) == REDIS_OK); |
|||
// redisProcessCallbacks(c); |
|||
// } |
|||
// test_cond(__test_callback_flags == 0x010203); |
|||
// redisFree(c); |
|||
// |
|||
// test("redisDisconnect executes pending callbacks with NULL reply: "); |
|||
// c = __connect_nonblock(); |
|||
// redisSetDisconnectCallback(c,__test_callback,(void*)1); |
|||
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); |
|||
// redisDisconnect(c); |
|||
// test_cond(__test_callback_flags == 0x0201); |
|||
// redisFree(c); |
|||
// } |
|||
|
|||
int main(int argc, char **argv) { |
|||
struct config cfg = { |
|||
.tcp = { |
|||
.host = "127.0.0.1", |
|||
.port = 6379 |
|||
}, |
|||
.unix = { |
|||
.path = "/tmp/redis.sock" |
|||
} |
|||
}; |
|||
int throughput = 1; |
|||
|
|||
/* Ignore broken pipe signal (for I/O error tests). */ |
|||
signal(SIGPIPE, SIG_IGN); |
|||
|
|||
/* Parse command line options. */ |
|||
argv++; argc--; |
|||
while (argc) { |
|||
if (argc >= 2 && !strcmp(argv[0],"-h")) { |
|||
argv++; argc--; |
|||
cfg.tcp.host = argv[0]; |
|||
} else if (argc >= 2 && !strcmp(argv[0],"-p")) { |
|||
argv++; argc--; |
|||
cfg.tcp.port = atoi(argv[0]); |
|||
} else if (argc >= 2 && !strcmp(argv[0],"-s")) { |
|||
argv++; argc--; |
|||
cfg.unix.path = argv[0]; |
|||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { |
|||
throughput = 0; |
|||
} else { |
|||
fprintf(stderr, "Invalid argument: %s\n", argv[0]); |
|||
exit(1); |
|||
} |
|||
argv++; argc--; |
|||
} |
|||
|
|||
test_format_commands(); |
|||
test_reply_reader(); |
|||
test_blocking_connection_errors(); |
|||
|
|||
printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); |
|||
cfg.type = CONN_TCP; |
|||
test_blocking_connection(cfg); |
|||
test_blocking_io_errors(cfg); |
|||
if (throughput) test_throughput(cfg); |
|||
|
|||
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); |
|||
cfg.type = CONN_UNIX; |
|||
test_blocking_connection(cfg); |
|||
test_blocking_io_errors(cfg); |
|||
if (throughput) test_throughput(cfg); |
|||
|
|||
if (fails) { |
|||
printf("*** %d TESTS FAILED ***\n", fails); |
|||
return 1; |
|||
} |
|||
|
|||
printf("ALL TESTS PASSED\n"); |
|||
return 0; |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue