| @ -0,0 +1,6 @@ | |||||
| /hiredis-test | |||||
| /hiredis-example* | |||||
| /*.o | |||||
| /*.so | |||||
| /*.dylib | |||||
| /*.a | |||||
| @ -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,139 @@ | |||||
| # Hiredis Makefile | |||||
| # Copyright (C) 2010 Salvatore Sanfilippo <antirez at gmail dot com> | |||||
| # This file is released under the BSD license, see the COPYING file | |||||
| OBJ=net.o hiredis.o sds.o async.o | |||||
| BINS=hiredis-example hiredis-test | |||||
| LIBNAME=libhiredis | |||||
| HIREDIS_MAJOR=0 | |||||
| HIREDIS_MINOR=10 | |||||
| uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') | |||||
| OPTIMIZATION?=-O3 | |||||
| ifeq ($(uname_S),SunOS) | |||||
| CFLAGS?=$(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF) | |||||
| CCLINK?=-ldl -lnsl -lsocket -lm -lpthread | |||||
| LDFLAGS?=-L. | |||||
| DYLIBSUFFIX=so | |||||
| STLIBSUFFIX=a | |||||
| DYLIB_MINOR_NAME?=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR) | |||||
| DYLIB_MAJOR_NAME?=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) | |||||
| DYLIBNAME?=$(LIBNAME).$(DYLIBSUFFIX) | |||||
| DYLIB_MAKE_CMD?=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) | |||||
| STLIBNAME?=$(LIBNAME).$(STLIBSUFFIX) | |||||
| STLIB_MAKE_CMD?=ar rcs $(STLIBNAME) | |||||
| INSTALL= cp -r | |||||
| else | |||||
| ifeq ($(uname_S),Darwin) | |||||
| CFLAGS?=$(OPTIMIZATION) -fPIC -Wall -W -Wstrict-prototypes -Wwrite-strings $(ARCH) $(PROF) | |||||
| CCLINK?=-lm -pthread | |||||
| LDFLAGS?=-L. | |||||
| OBJARCH?=-arch i386 -arch x86_64 | |||||
| DYLIBSUFFIX=dylib | |||||
| STLIBSUFFIX=a | |||||
| DYLIB_MINOR_NAME?=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX) | |||||
| DYLIB_MAJOR_NAME?=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX) | |||||
| DYLIBNAME?=$(LIBNAME).$(DYLIBSUFFIX) | |||||
| DYLIB_MAKE_CMD?=libtool -dynamic -o $(DYLIBNAME) -install_name $(DYLIB_MINOR_NAME) -lm $(DEBUG) - | |||||
| STLIBNAME?=$(LIBNAME).$(STLIBSUFFIX) | |||||
| STLIB_MAKE_CMD?=libtool -static -o $(STLIBNAME) - | |||||
| INSTALL= cp -a | |||||
| else | |||||
| CFLAGS?=$(OPTIMIZATION) -fPIC -Wall -W -Wstrict-prototypes -Wwrite-strings $(ARCH) $(PROF) | |||||
| CCLINK?=-lm -pthread | |||||
| LDFLAGS?=-L. | |||||
| DYLIBSUFFIX=so | |||||
| STLIBSUFFIX=a | |||||
| DYLIB_MINOR_NAME?=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR) | |||||
| DYLIB_MAJOR_NAME?=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) | |||||
| DYLIBNAME?=$(LIBNAME).$(DYLIBSUFFIX) | |||||
| DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) | |||||
| STLIBNAME?=$(LIBNAME).$(STLIBSUFFIX) | |||||
| STLIB_MAKE_CMD?=ar rcs $(STLIBNAME) | |||||
| INSTALL= cp -a | |||||
| endif | |||||
| endif | |||||
| CCOPT= $(CFLAGS) $(CCLINK) | |||||
| DEBUG?= -g -ggdb | |||||
| PREFIX?=/usr/local | |||||
| INCLUDE_PATH?=include/hiredis | |||||
| LIBRARY_PATH?=lib | |||||
| INSTALL_INCLUDE_PATH= $(PREFIX)/$(INCLUDE_PATH) | |||||
| INSTALL_LIBRARY_PATH= $(PREFIX)/$(LIBRARY_PATH) | |||||
| all: $(DYLIBNAME) $(BINS) | |||||
| # Deps (use make dep to generate this) | |||||
| net.o: net.c fmacros.h net.h hiredis.h | |||||
| async.o: async.c async.h hiredis.h sds.h dict.c dict.h | |||||
| example.o: example.c hiredis.h | |||||
| hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.h | |||||
| sds.o: sds.c sds.h | |||||
| test.o: test.c hiredis.h | |||||
| $(DYLIBNAME): $(OBJ) | |||||
| $(DYLIB_MAKE_CMD) $(OBJ) | |||||
| $(STLIBNAME): $(OBJ) | |||||
| $(STLIB_MAKE_CMD) $(OBJ) | |||||
| dynamic: $(DYLIBNAME) | |||||
| static: $(STLIBNAME) | |||||
| # Binaries: | |||||
| hiredis-example-libevent: example-libevent.c adapters/libevent.h $(STLIBNAME) | |||||
| $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -levent example-libevent.c $(STLIBNAME) | |||||
| hiredis-example-libev: example-libev.c adapters/libev.h $(STLIBNAME) | |||||
| $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lev example-libev.c $(STLIBNAME) | |||||
| ifndef AE_DIR | |||||
| hiredis-example-ae: | |||||
| @echo "Please specify AE_DIR (e.g. <redis repository>/src)" | |||||
| @false | |||||
| else | |||||
| hiredis-example-ae: example-ae.c adapters/ae.h $(STLIBNAME) | |||||
| $(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o example-ae.c $(STLIBNAME) | |||||
| endif | |||||
| hiredis-%: %.o $(STLIBNAME) | |||||
| $(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) $< $(STLIBNAME) | |||||
| test: hiredis-test | |||||
| ./hiredis-test | |||||
| .c.o: | |||||
| $(CC) -std=c99 -pedantic -c $(CFLAGS) $(OBJARCH) $(DEBUG) $(COMPILE_TIME) $< | |||||
| clean: | |||||
| rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov | |||||
| dep: | |||||
| $(CC) -MM *.c | |||||
| install: $(DYLIBNAME) $(STLIBNAME) | |||||
| mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) | |||||
| $(INSTALL) hiredis.h async.h adapters $(INSTALL_INCLUDE_PATH) | |||||
| $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) | |||||
| cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) | |||||
| cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME) | |||||
| $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) | |||||
| 32bit: | |||||
| @echo "" | |||||
| @echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386" | |||||
| @echo "" | |||||
| $(MAKE) ARCH="-m32" | |||||
| gprof: | |||||
| $(MAKE) PROF="-pg" | |||||
| gcov: | |||||
| $(MAKE) PROF="-fprofile-arcs -ftest-coverage" | |||||
| noopt: | |||||
| $(MAKE) OPTIMIZATION="" | |||||
| @ -0,0 +1,345 @@ | |||||
| # 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). | |||||
| ### 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,2 @@ | |||||
| - add redisCommandVector() | |||||
| - add support for pipelining | |||||
| @ -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,538 @@ | |||||
| /* | |||||
| * 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 <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) { | |||||
| /* No more regular callbacks, 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,14 @@ | |||||
| #ifndef __HIREDIS_FMACRO_H | |||||
| #define __HIREDIS_FMACRO_H | |||||
| #ifndef _BSD_SOURCE | |||||
| #define _BSD_SOURCE | |||||
| #endif | |||||
| #ifdef __linux__ | |||||
| #define _XOPEN_SOURCE 700 | |||||
| #else | |||||
| #define _XOPEN_SOURCE | |||||
| #endif | |||||
| #endif | |||||
| @ -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 0 | |||||
| #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,528 @@ | |||||
| #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" | |||||
| /* 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 int use_unix = 0; | |||||
| static redisContext *blocking_context = NULL; | |||||
| static void __connect(redisContext **target) { | |||||
| *target = blocking_context = (use_unix ? | |||||
| redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379)); | |||||
| if (blocking_context->err) { | |||||
| printf("Connection error: %s\n", blocking_context->errstr); | |||||
| exit(1); | |||||
| } | |||||
| } | |||||
| 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_blocking_connection(void) { | |||||
| redisContext *c; | |||||
| redisReply *reply; | |||||
| int major, minor; | |||||
| 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", 56380); | |||||
| test_cond(c->err == REDIS_ERR_IO && | |||||
| strcmp(c->errstr,"Connection refused") == 0); | |||||
| redisFree(c); | |||||
| __connect(&c); | |||||
| test("Is able to deliver commands: "); | |||||
| reply = redisCommand(c,"PING"); | |||||
| test_cond(reply->type == REDIS_REPLY_STATUS && | |||||
| strcasecmp(reply->str,"pong") == 0) | |||||
| freeReplyObject(reply); | |||||
| /* Switch to DB 9 for testing, now that we know we can chat. */ | |||||
| reply = redisCommand(c,"SELECT 9"); | |||||
| freeReplyObject(reply); | |||||
| /* Make sure the DB is emtpy */ | |||||
| reply = redisCommand(c,"DBSIZE"); | |||||
| if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) { | |||||
| printf("Database #9 is not empty, test can not continue\n"); | |||||
| exit(1); | |||||
| } | |||||
| 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); | |||||
| { | |||||
| /* Find out Redis version to determine the path for the next test */ | |||||
| const char *field = "redis_version:"; | |||||
| char *p, *eptr; | |||||
| reply = redisCommand(c,"INFO"); | |||||
| p = strstr(reply->str,field); | |||||
| major = strtol(p+strlen(field),&eptr,10); | |||||
| p = eptr+1; /* char next to the first "." */ | |||||
| minor = strtol(p,&eptr,10); | |||||
| freeReplyObject(reply); | |||||
| } | |||||
| test("Returns I/O error when the connection is lost: "); | |||||
| reply = redisCommand(c,"QUIT"); | |||||
| if (major >= 2 && minor > 0) { | |||||
| /* > 2.0 returns OK on QUIT and read() should be issued once more | |||||
| * to know the descriptor is at EOF. */ | |||||
| test_cond(strcasecmp(reply->str,"OK") == 0 && | |||||
| redisGetReply(c,(void**)&reply) == REDIS_ERR); | |||||
| freeReplyObject(reply); | |||||
| } else { | |||||
| test_cond(reply == NULL); | |||||
| } | |||||
| /* On 2.0, QUIT will cause the connection to be closed immediately and | |||||
| * the read(2) for the reply on QUIT will set the error to EOF. | |||||
| * On >2.0, QUIT will return with OK and another read(2) needed to be | |||||
| * issued to find out the socket was closed by the server. In both | |||||
| * conditions, the error will be set to EOF. */ | |||||
| assert(c->err == REDIS_ERR_EOF && | |||||
| strcmp(c->errstr,"Server closed the connection") == 0); | |||||
| redisFree(c); | |||||
| __connect(&c); | |||||
| test("Returns I/O error on socket timeout: "); | |||||
| struct timeval tv = { 0, 1000 }; | |||||
| assert(redisSetTimeout(c,tv) == REDIS_OK); | |||||
| test_cond(redisGetReply(c,(void**)&reply) == REDIS_ERR && | |||||
| c->err == REDIS_ERR_IO && errno == EAGAIN); | |||||
| redisFree(c); | |||||
| /* Context should be connected */ | |||||
| __connect(&c); | |||||
| } | |||||
| 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); | |||||
| } | |||||
| static void test_throughput(void) { | |||||
| int i, num; | |||||
| long long t1, t2; | |||||
| redisContext *c = blocking_context; | |||||
| redisReply **replies; | |||||
| 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); | |||||
| } | |||||
| static void cleanup(void) { | |||||
| redisContext *c = blocking_context; | |||||
| 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); | |||||
| redisFree(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", 6379, 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) { | |||||
| if (argc > 1) { | |||||
| if (strcmp(argv[1],"-s") == 0) | |||||
| use_unix = 1; | |||||
| } | |||||
| signal(SIGPIPE, SIG_IGN); | |||||
| test_format_commands(); | |||||
| test_blocking_connection(); | |||||
| test_reply_reader(); | |||||
| // test_nonblocking_connection(); | |||||
| test_throughput(); | |||||
| cleanup(); | |||||
| if (fails == 0) { | |||||
| printf("ALL TESTS PASSED\n"); | |||||
| } else { | |||||
| printf("*** %d TESTS FAILED ***\n", fails); | |||||
| } | |||||
| return 0; | |||||
| } | |||||