summaryrefslogtreecommitdiff
path: root/net.c
diff options
context:
space:
mode:
authorPieter Noordhuis <pcnoordhuis@gmail.com>2011-02-04 15:26:28 +0100
committerPieter Noordhuis <pcnoordhuis@gmail.com>2011-02-04 15:26:28 +0100
commit2fc0d8756e547dcc10f5337d2e60d2b91da6e467 (patch)
treebce5ae757cf8d211266b432be31d979ba89eae2a /net.c
parent663d6d1258b3ef49178ed4120de5c91ecffe2512 (diff)
Use select(2) for enforce a timeout on blocking connect(2)
Diffstat (limited to 'net.c')
-rw-r--r--net.c103
1 files changed, 85 insertions, 18 deletions
diff --git a/net.c b/net.c
index 75cbe54..44b338b 100644
--- a/net.c
+++ b/net.c
@@ -33,6 +33,7 @@
#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>
@@ -67,7 +68,7 @@ static int redisCreateSocket(redisContext *c, int type) {
return s;
}
-static int redisSetNonBlock(redisContext *c, int fd) {
+static int redisSetBlocking(redisContext *c, int fd, int blocking) {
int flags;
/* Set the socket nonblocking.
@@ -79,9 +80,15 @@ static int redisSetNonBlock(redisContext *c, int fd) {
close(fd);
return REDIS_ERR;
}
- if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+
+ if (blocking)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) == -1) {
__redisSetError(c,REDIS_ERR_IO,
- sdscatprintf(sdsempty(), "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)));
+ sdscatprintf(sdsempty(), "fcntl(F_SETFL): %s", strerror(errno)));
close(fd);
return REDIS_ERR;
}
@@ -93,11 +100,67 @@ static int redisSetTcpNoDelay(redisContext *c, int fd) {
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetError(c,REDIS_ERR_IO,
sdscatprintf(sdsempty(), "setsockopt(TCP_NODELAY): %s", strerror(errno)));
+ 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) {
+ memcpy(&to,timeout,sizeof(timeout));
+ toptr = &to;
+ }
+
+ if (errno == EINPROGRESS) {
+ FD_ZERO(&wfd);
+ FD_SET(fd, &wfd);
+
+ if (select(FD_SETSIZE, NULL, &wfd, NULL, toptr) == -1) {
+ __redisSetError(c,REDIS_ERR_IO,
+ sdscatprintf(sdsempty(), "select(2): %s", strerror(errno)));
+ close(fd);
+ return REDIS_ERR;
+ }
+
+ if (!FD_ISSET(fd, &wfd)) {
+ errno = ETIMEDOUT;
+ __redisSetError(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) {
+ __redisSetError(c,REDIS_ERR_IO,
+ sdscatprintf(sdsempty(), "getsockopt(SO_ERROR): %s", strerror(errno)));
+ close(fd);
+ return REDIS_ERR;
+ }
+
+ if (err) {
+ errno = err;
+ __redisSetError(c,REDIS_ERR_IO,NULL);
+ close(fd);
+ return REDIS_ERR;
+ }
+
+ return REDIS_OK;
+ }
+
+ __redisSetError(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) {
__redisSetError(c,REDIS_ERR_IO,
@@ -112,14 +175,14 @@ int redisContextSetTimeout(redisContext *c, struct timeval tv) {
return REDIS_OK;
}
-int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
+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)) == REDIS_ERR)
+ if ((s = redisCreateSocket(c,AF_INET)) < 0)
return REDIS_ERR;
- if (!blocking && redisSetNonBlock(c,s) == REDIS_ERR)
+ if (redisSetBlocking(c,s,0) != REDIS_OK)
return REDIS_ERR;
sa.sin_family = AF_INET;
@@ -141,30 +204,31 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
- close(s);
- return REDIS_ERR;
+ if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
+ return REDIS_ERR;
}
}
- if (redisSetTcpNoDelay(c,s) != REDIS_OK) {
- close(s);
+ /* 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) {
+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)) == REDIS_ERR)
+ if ((s = redisCreateSocket(c,AF_LOCAL)) < 0)
return REDIS_ERR;
- if (!blocking && redisSetNonBlock(c,s) != REDIS_OK)
+ if (redisSetBlocking(c,s,0) != REDIS_OK)
return REDIS_ERR;
sa.sun_family = AF_LOCAL;
@@ -173,12 +237,15 @@ int redisContextConnectUnix(redisContext *c, const char *path) {
if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
- close(s);
- return REDIS_ERR;
+ 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;