diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .travis.yml | 6 | ||||
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | Makefile | 42 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | adapters/libuv.h | 120 | ||||
-rw-r--r-- | async.c | 35 | ||||
-rw-r--r-- | examples/example-ae.c (renamed from example-ae.c) | 14 | ||||
-rw-r--r-- | examples/example-libev.c (renamed from example-libev.c) | 7 | ||||
-rw-r--r-- | examples/example-libevent.c (renamed from example-libevent.c) | 7 | ||||
-rw-r--r-- | examples/example-libuv.c | 53 | ||||
-rw-r--r-- | examples/example.c (renamed from example.c) | 16 | ||||
-rw-r--r-- | fmacros.h | 4 | ||||
-rw-r--r-- | hiredis.c | 55 | ||||
-rw-r--r-- | hiredis.h | 3 | ||||
-rw-r--r-- | net.c | 42 | ||||
-rw-r--r-- | net.h | 1 | ||||
-rw-r--r-- | sds.c | 1 | ||||
-rw-r--r-- | test.c | 44 |
19 files changed, 409 insertions, 54 deletions
@@ -1,5 +1,5 @@ /hiredis-test -/hiredis-example* +/examples/hiredis-example* /*.o /*.so /*.dylib diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..030427f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: c +compiler: + - gcc + - clang + +script: make && make check diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a57b81..268b15c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ### 0.11.0 -* Increase the maximum multi-bulk reply depth to 8. +* Increase the maximum multi-bulk reply depth to 7. * Increase the read buffer size from 2k to 16k. @@ -4,7 +4,8 @@ # 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 +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev +TESTS=hiredis-test LIBNAME=libhiredis HIREDIS_MAJOR=0 @@ -41,12 +42,11 @@ ifeq ($(uname_S),Darwin) DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) endif -all: $(DYLIBNAME) $(BINS) +all: $(DYLIBNAME) # 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 @@ -61,22 +61,36 @@ dynamic: $(DYLIBNAME) static: $(STLIBNAME) # Binaries: -hiredis-example-libevent: example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -levent example-libevent.c $(STLIBNAME) +hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) -hiredis-example-libev: example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -lev example-libev.c $(STLIBNAME) +hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(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 $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I$(AE_DIR) $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o example-ae.c $(STLIBNAME) +hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) endif -hiredis-%: %.o $(STLIBNAME) +ifndef LIBUV_DIR +hiredis-example-libuv: + @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" + @false +else +hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME) +endif + +hiredis-example: examples/example.c $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + +examples: $(EXAMPLES) + +hiredis-test: test.o $(STLIBNAME) $(CC) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) test: hiredis-test @@ -98,17 +112,15 @@ check: hiredis-test $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< clean: - rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov + rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) examples/hiredis-example* *.o *.gcda *.gcno *.gcov dep: $(CC) -MM *.c # Installation related variables and target PREFIX?=/usr/local -INCLUDE_PATH?=include/hiredis -LIBRARY_PATH?=lib -INSTALL_INCLUDE_PATH= $(PREFIX)/$(INCLUDE_PATH) -INSTALL_LIBRARY_PATH= $(PREFIX)/$(LIBRARY_PATH) +INSTALL_INCLUDE_PATH= $(PREFIX)/include/hiredis +INSTALL_LIBRARY_PATH= $(PREFIX)/lib ifeq ($(uname_S),SunOS) INSTALL?= cp -r @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) + # HIREDIS Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. @@ -44,7 +46,7 @@ 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) { + if (c != NULL && c->err) { printf("Error: %s\n", c->errstr); // handle error } @@ -66,7 +68,7 @@ When you need to pass binary safe strings in a command, the `%b` specifier can b 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); + reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); Internally, Hiredis splits the command in different arguments and will convert it to the protocol used to communicate with Redis. @@ -337,6 +339,9 @@ 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). +The parser limits the level of nesting for multi bulk payloads to 7. If the +multi bulk nesting level is higher than this, the parser returns an error. + ### Customizing replies The function `redisReaderGetReply` creates `redisReply` and makes the function diff --git a/adapters/libuv.h b/adapters/libuv.h new file mode 100644 index 0000000..e61d5ca --- /dev/null +++ b/adapters/libuv.h @@ -0,0 +1,120 @@ +#ifndef __HIREDIS_LIBUV_H__ +#define __HIREDIS_LIBUV_H__ +#include <uv.h> +#include "../hiredis.h" +#include "../async.h" + + +typedef struct redisLibuvEvents { + redisAsyncContext* context; + uv_poll_t handle; + int events; +} redisLibuvEvents; + + +static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + if (status != 0) { + return; + } + + if (events & UV_READABLE) { + redisAsyncHandleRead(p->context); + } + if (events & UV_WRITABLE) { + redisAsyncHandleWrite(p->context); + } +} + + +static void redisLibuvAddRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_READABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_READABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void redisLibuvAddWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_WRITABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_WRITABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void on_close(uv_handle_t* handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + free(p); +} + + +static void redisLibuvCleanup(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + uv_close((uv_handle_t*)&p->handle, on_close); +} + + +int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { + redisContext *c = &(ac->c); + + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + + redisLibuvEvents* p = malloc(sizeof(*p)); + + if (!p) { + return REDIS_ERR; + } + + memset(p, 0, sizeof(*p)); + + if (uv_poll_init(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } + + ac->ev.data = p; + p->handle.data = p; + p->context = ac; + + return REDIS_OK; +} +#endif @@ -103,7 +103,12 @@ static dictType callbackDict = { }; static redisAsyncContext *redisAsyncInitialize(redisContext *c) { - redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext)); + redisAsyncContext *ac; + + ac = realloc(c,sizeof(redisAsyncContext)); + if (ac == NULL) + return NULL; + c = &(ac->c); /* The regular connect functions will always set the flag REDIS_CONNECTED. @@ -143,15 +148,32 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) { } redisAsyncContext *redisAsyncConnect(const char *ip, int port) { - redisContext *c = redisConnectNonBlock(ip,port); - redisAsyncContext *ac = redisAsyncInitialize(c); + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectNonBlock(ip,port); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + __redisAsyncCopyError(ac); return ac; } redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisContext *c = redisConnectUnixNonBlock(path); - redisAsyncContext *ac = redisAsyncInitialize(c); + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectUnixNonBlock(path); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); __redisAsyncCopyError(ac); return ac; } @@ -183,6 +205,9 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { /* Copy callback from stack to heap */ cb = malloc(sizeof(*cb)); + if (cb == NULL) + return REDIS_ERR_OOM; + if (source != NULL) { memcpy(cb,source,sizeof(*cb)); cb->next = NULL; diff --git a/example-ae.c b/examples/example-ae.c index 5ed34a3..8efa730 100644 --- a/example-ae.c +++ b/examples/example-ae.c @@ -2,9 +2,10 @@ #include <stdlib.h> #include <string.h> #include <signal.h> -#include "hiredis.h" -#include "async.h" -#include "adapters/ae.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; @@ -21,17 +22,22 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); + aeStop(loop); return; } + printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); + aeStop(loop); return; } + printf("Disconnected...\n"); + aeStop(loop); } int main (int argc, char **argv) { @@ -44,7 +50,7 @@ int main (int argc, char **argv) { return 1; } - loop = aeCreateEventLoop(); + loop = aeCreateEventLoop(64); redisAeAttach(loop, c); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); diff --git a/example-libev.c b/examples/example-libev.c index 7894f1f..cc8b166 100644 --- a/example-libev.c +++ b/examples/example-libev.c @@ -2,9 +2,10 @@ #include <stdlib.h> #include <string.h> #include <signal.h> -#include "hiredis.h" -#include "async.h" -#include "adapters/libev.h" + +#include <hiredis.h> +#include <async.h> +#include <adapters/libev.h> void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; diff --git a/example-libevent.c b/examples/example-libevent.c index 9da8e02..d333c22 100644 --- a/example-libevent.c +++ b/examples/example-libevent.c @@ -2,9 +2,10 @@ #include <stdlib.h> #include <string.h> #include <signal.h> -#include "hiredis.h" -#include "async.h" -#include "adapters/libevent.h" + +#include <hiredis.h> +#include <async.h> +#include <adapters/libevent.h> void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; diff --git a/examples/example-libuv.c b/examples/example-libuv.c new file mode 100644 index 0000000..a5462d4 --- /dev/null +++ b/examples/example-libuv.c @@ -0,0 +1,53 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> + +#include <hiredis.h> +#include <async.h> +#include <adapters/libuv.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, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + uv_loop_t* loop = uv_default_loop(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibuvAttach(c,loop); + 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"); + uv_run(loop, UV_RUN_DEFAULT); + return 0; +} diff --git a/example.c b/examples/example.c index 378ef71..c76451f 100644 --- a/example.c +++ b/examples/example.c @@ -2,7 +2,7 @@ #include <stdlib.h> #include <string.h> -#include "hiredis.h" +#include <hiredis.h> int main(void) { unsigned int j; @@ -11,8 +11,13 @@ int main(void) { struct timeval timeout = { 1, 500000 }; // 1.5 seconds c = redisConnectWithTimeout((char*)"127.0.0.1", 6379, timeout); - if (c->err) { - printf("Connection error: %s\n", c->errstr); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } exit(1); } @@ -27,7 +32,7 @@ int main(void) { freeReplyObject(reply); /* Set a key using binary safe API */ - reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5); + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); printf("SET (binary API): %s\n", reply->str); freeReplyObject(reply); @@ -64,5 +69,8 @@ int main(void) { } freeReplyObject(reply); + /* Disconnects and frees the context */ + redisFree(c); + return 0; } @@ -13,4 +13,8 @@ #define _XOPEN_SOURCE #endif +#if __APPLE__ && __MACH__ +#define _OSX +#endif + #endif @@ -917,7 +917,7 @@ err: * %b represents a binary safe string * * When using %b you need to provide both the pointer to the string - * and the length in bytes. Examples: + * and the length in bytes as a size_t. Examples: * * len = redisFormatCommand(target, "GET %s", mykey); * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); @@ -1014,42 +1014,72 @@ void redisFree(redisContext *c) { * context will be set to the return value of the error function. * When no set of reply functions is given, the default set will be used. */ redisContext *redisConnect(const char *ip, int port) { - redisContext *c = redisContextInit(); + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags |= REDIS_BLOCK; redisContextConnectTcp(c,ip,port,NULL); return c; } redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval tv) { - redisContext *c = redisContextInit(); + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags |= REDIS_BLOCK; redisContextConnectTcp(c,ip,port,&tv); return c; } redisContext *redisConnectNonBlock(const char *ip, int port) { - redisContext *c = redisContextInit(); + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; redisContextConnectTcp(c,ip,port,NULL); return c; } redisContext *redisConnectUnix(const char *path) { - redisContext *c = redisContextInit(); + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags |= REDIS_BLOCK; redisContextConnectUnix(c,path,NULL); return c; } redisContext *redisConnectUnixWithTimeout(const char *path, struct timeval tv) { - redisContext *c = redisContextInit(); + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags |= REDIS_BLOCK; redisContextConnectUnix(c,path,&tv); return c; } redisContext *redisConnectUnixNonBlock(const char *path) { - redisContext *c = redisContextInit(); + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + c->flags &= ~REDIS_BLOCK; redisContextConnectUnix(c,path,NULL); return c; @@ -1062,6 +1092,13 @@ int redisSetTimeout(redisContext *c, struct timeval tv) { return REDIS_ERR; } +/* Enable connection KeepAlive. */ +int redisEnableKeepAlive(redisContext *c) { + if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) + return REDIS_ERR; + return REDIS_OK; +} + /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * @@ -1077,7 +1114,7 @@ int redisBufferRead(redisContext *c) { nread = read(c->fd,buf,sizeof(buf)); if (nread == -1) { - if (errno == EAGAIN && !(c->flags & REDIS_BLOCK)) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ } else { __redisSetError(c,REDIS_ERR_IO,NULL); @@ -1114,7 +1151,7 @@ int redisBufferWrite(redisContext *c, int *done) { if (sdslen(c->obuf) > 0) { nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); if (nwritten == -1) { - if (errno == EAGAIN && !(c->flags & REDIS_BLOCK)) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ } else { __redisSetError(c,REDIS_ERR_IO,NULL); @@ -88,6 +88,8 @@ #define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ +#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ + #ifdef __cplusplus extern "C" { #endif @@ -177,6 +179,7 @@ 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); +int redisEnableKeepAlive(redisContext *c); void redisFree(redisContext *c); int redisBufferRead(redisContext *c); int redisBufferWrite(redisContext *c, int *done); @@ -55,7 +55,7 @@ void __redisSetError(redisContext *c, int type, const char *str); static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { - char buf[128]; + char buf[128] = { 0 }; size_t len = 0; if (prefix != NULL) @@ -113,6 +113,45 @@ static int redisSetBlocking(redisContext *c, int fd, int blocking) { return REDIS_OK; } +int redisKeepAlive(redisContext *c, int interval) { + int val = 1; + int fd = c->fd; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + +#ifdef _OSX + val = interval; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#else + val = interval; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval/3; + if (val == 0) val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#endif + + return REDIS_OK; +} + static int redisSetTcpNoDelay(redisContext *c, int fd) { int yes = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { @@ -136,6 +175,7 @@ static int redisContextWaitReady(redisContext *c, int fd, const struct timeval * /* Only use timeout when not NULL. */ if (timeout != NULL) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); close(fd); return REDIS_ERR; } @@ -43,5 +43,6 @@ int redisCheckSocketError(redisContext *c, int fd); 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); +int redisKeepAlive(redisContext *c, int interval); #endif @@ -178,6 +178,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { buf[buflen-2] = '\0'; va_copy(cpy,ap); vsnprintf(buf, buflen, fmt, cpy); + va_end(cpy); if (buf[buflen-2] != '\0') { free(buf); buflen *= 2; @@ -8,6 +8,7 @@ #include <unistd.h> #include <signal.h> #include <errno.h> +#include <limits.h> #include "hiredis.h" @@ -22,6 +23,7 @@ struct config { struct { const char *host; int port; + struct timeval timeout; } tcp; struct { @@ -88,7 +90,10 @@ static redisContext *connect(struct config config) { assert(NULL); } - if (c->err) { + if (c == NULL) { + printf("Connection error: can't allocate redis context\n"); + exit(1); + } else if (c->err) { printf("Connection error: %s\n", c->errstr); exit(1); } @@ -125,13 +130,13 @@ static void test_format_commands(void) { free(cmd); test("Format command with %%b string interpolation: "); - len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)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); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)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); @@ -177,7 +182,7 @@ static void test_format_commands(void) { FLOAT_WIDTH_TEST(double); test("Format command with invalid printf format: "); - len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",3); + len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); test_cond(len == -1); const char *argv[3]; @@ -286,7 +291,9 @@ static void test_blocking_connection_errors(void) { c = redisConnect((char*)"idontexist.local", 6379); test_cond(c->err == REDIS_ERR_OTHER && (strcmp(c->errstr,"Name or service not known") == 0 || - strcmp(c->errstr,"Can't resolve: idontexist.local") == 0)); + strcmp(c->errstr,"Can't resolve: idontexist.local") == 0 || + strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr,"no address associated with name") == 0)); redisFree(c); test("Returns error when the port is not open: "); @@ -328,7 +335,7 @@ static void test_blocking_connection(struct config config) { freeReplyObject(reply); test("%%b String interpolation works: "); - reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11); + reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); freeReplyObject(reply); reply = redisCommand(c,"GET foo"); test_cond(reply->type == REDIS_REPLY_STRING && @@ -430,6 +437,30 @@ static void test_blocking_io_errors(struct config config) { redisFree(c); } +static void test_invalid_timeout_errors(struct config config) { + redisContext *c; + + test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = 0; + config.tcp.timeout.tv_usec = 10000001; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO); + + test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; + config.tcp.timeout.tv_usec = 0; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO); + + redisFree(c); +} + static void test_throughput(struct config config) { redisContext *c = connect(config); redisReply **replies; @@ -638,6 +669,7 @@ int main(int argc, char **argv) { cfg.type = CONN_TCP; test_blocking_connection(cfg); test_blocking_io_errors(cfg); + test_invalid_timeout_errors(cfg); if (throughput) test_throughput(cfg); printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); |