From 7c4d2557c4bbc637514392cb725719790b50f677 Mon Sep 17 00:00:00 2001 From: mike Date: Thu, 14 Aug 2014 16:24:59 -0400 Subject: Add support for SO_REUSEADDR [This introduces some new API functions.] * Adds new flag to the connection context indicating SO_REUSEADDR should be set. * Adds max number of retries constant for when connect() hits EADDRNOTAVAIL. * Adds new function, redisAsyncConnectBindWithReuse(), letting clients enable this functionality. [Removed trailing whitespace in new header lines.] Closes #264 --- async.c | 8 ++++++++ async.h | 2 ++ hiredis.c | 9 +++++++++ hiredis.h | 12 +++++++++++- net.c | 20 +++++++++++++++++++- 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/async.c b/async.c index f236544..8b50de2 100644 --- a/async.c +++ b/async.c @@ -173,6 +173,14 @@ redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, return ac; } +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisContext *c; redisAsyncContext *ac; diff --git a/async.h b/async.h index 67c75ff..59cbf46 100644 --- a/async.h +++ b/async.h @@ -103,6 +103,8 @@ typedef struct redisAsyncContext { /* Functions that proxy to hiredis */ redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr); redisAsyncContext *redisAsyncConnectUnix(const char *path); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); diff --git a/hiredis.c b/hiredis.c index 4dfd863..be67658 100644 --- a/hiredis.c +++ b/hiredis.c @@ -1123,6 +1123,15 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port, return c; } +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + c->flags &= ~REDIS_BLOCK; + c->flags |= REDIS_REUSEADDR; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + redisContext *redisConnectUnix(const char *path) { redisContext *c; diff --git a/hiredis.h b/hiredis.h index fa3a89d..8e8aa03 100644 --- a/hiredis.h +++ b/hiredis.h @@ -80,6 +80,9 @@ /* Flag that is set when monitor mode is active */ #define REDIS_MONITORING 0x40 +/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ +#define REDIS_REUSEADDR 0x80 + #define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 #define REDIS_REPLY_INTEGER 3 @@ -91,6 +94,10 @@ #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ +/* number of times we retry to connect in the case of EADDRNOTAVAIL and + * SO_REUSEADDR is being used. */ +#define REDIS_CONNECT_RETRIES 10 + #ifdef __cplusplus extern "C" { #endif @@ -177,7 +184,10 @@ typedef struct redisContext { redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); -redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr); +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr); redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); diff --git a/net.c b/net.c index bdb84ce..0bea81e 100644 --- a/net.c +++ b/net.c @@ -256,10 +256,12 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { - int s, rv; + int s, rv, n; char _port[6]; /* strlen("65535"); */ struct addrinfo hints, *servinfo, *bservinfo, *p, *b; int blocking = (c->flags & REDIS_BLOCK); + int reuseaddr = (c->flags & REDIS_REUSEADDR); + int reuses = 0; snprintf(_port, 6, "%d", port); memset(&hints,0,sizeof(hints)); @@ -279,6 +281,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, } } for (p = servinfo; p != NULL; p = p->ai_next) { +addrretry: if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) continue; @@ -294,6 +297,15 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, __redisSetError(c,REDIS_ERR_OTHER,buf); goto error; } + + if (reuseaddr) { + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, + sizeof(n)) < 0) { + goto error; + } + } + for (b = bservinfo; b != NULL; b = b->ai_next) { if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { bound = 1; @@ -314,6 +326,12 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, continue; } else if (errno == EINPROGRESS && !blocking) { /* This is ok. */ + } else if (errno == EADDRNOTAVAIL && reuseaddr) { + if (++reuses >= REDIS_CONNECT_RETRIES) { + goto error; + } else { + goto addrretry; + } } else { if (redisContextWaitReady(c,timeout) != REDIS_OK) goto error; -- cgit v1.2.3