summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml6
-rw-r--r--CHANGELOG.md2
-rw-r--r--Makefile42
-rw-r--r--README.md9
-rw-r--r--adapters/libuv.h120
-rw-r--r--async.c35
-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.c53
-rw-r--r--examples/example.c (renamed from example.c)16
-rw-r--r--fmacros.h4
-rw-r--r--hiredis.c55
-rw-r--r--hiredis.h3
-rw-r--r--net.c42
-rw-r--r--net.h1
-rw-r--r--sds.c1
-rw-r--r--test.c44
19 files changed, 409 insertions, 54 deletions
diff --git a/.gitignore b/.gitignore
index 1a4d60d..0c166a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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.
diff --git a/Makefile b/Makefile
index 16b8767..c8632b4 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index 62fe106..dba4a8c 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/async.c b/async.c
index 2bbb57d..9d7cad1 100644
--- a/async.c
+++ b/async.c
@@ -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;
}
diff --git a/fmacros.h b/fmacros.h
index 21cd9cf..799c12c 100644
--- a/fmacros.h
+++ b/fmacros.h
@@ -13,4 +13,8 @@
#define _XOPEN_SOURCE
#endif
+#if __APPLE__ && __MACH__
+#define _OSX
+#endif
+
#endif
diff --git a/hiredis.c b/hiredis.c
index 8f11950..65f84d0 100644
--- a/hiredis.c
+++ b/hiredis.c
@@ -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);
diff --git a/hiredis.h b/hiredis.h
index aadcf35..42290a1 100644
--- a/hiredis.h
+++ b/hiredis.h
@@ -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);
diff --git a/net.c b/net.c
index 82ab2b4..96ca63d 100644
--- a/net.c
+++ b/net.c
@@ -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;
}
diff --git a/net.h b/net.h
index eb8a0a1..36096fe 100644
--- a/net.h
+++ b/net.h
@@ -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
diff --git a/sds.c b/sds.c
index 0af9c67..9226799 100644
--- a/sds.c
+++ b/sds.c
@@ -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;
diff --git a/test.c b/test.c
index 737ad1c..b3b806f 100644
--- a/test.c
+++ b/test.c
@@ -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);