From 02c697cb04e1d2a15a5226abe210abdb4c010ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20S=C3=B6derqvist?= Date: Thu, 25 May 2023 17:26:12 +0200 Subject: [PATCH 1/2] Implement redisSetTcpUserTimeout to set socket option TCP_USER_TIMEOUT --- hiredis.c | 5 +++++ hiredis.h | 1 + net.c | 16 ++++++++++++++++ net.h | 1 + 4 files changed, 23 insertions(+) diff --git a/hiredis.c b/hiredis.c index 72502eb7e..9d8a500c7 100644 --- a/hiredis.c +++ b/hiredis.c @@ -953,6 +953,11 @@ int redisEnableKeepAlive(redisContext *c) { return redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL); } +/* Set the socket option TCP_USER_TIMEOUT. */ +int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout) { + return redisContextSetTcpUserTimeout(c, timeout); +} + /* Set a user provided RESP3 PUSH handler and return any old one set. */ redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) { redisPushFn *old = c->push_cb; diff --git a/hiredis.h b/hiredis.h index f49005956..2291d3eba 100644 --- a/hiredis.h +++ b/hiredis.h @@ -323,6 +323,7 @@ redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn); int redisSetTimeout(redisContext *c, const struct timeval tv); int redisEnableKeepAlive(redisContext *c); int redisEnableKeepAliveWithInterval(redisContext *c, int interval); +int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout); void redisFree(redisContext *c); redisFD redisFreeKeepFd(redisContext *c); int redisBufferRead(redisContext *c); diff --git a/net.c b/net.c index ec96412c6..ccd7f166a 100644 --- a/net.c +++ b/net.c @@ -228,6 +228,22 @@ int redisSetTcpNoDelay(redisContext *c) { return REDIS_OK; } +int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout) { + int res; +#ifdef TCP_USER_TIMEOUT + res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout)); +#else + res = -1; + (void)timeout; +#endif + if (res == -1); { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_USER_TIMEOUT)"); + redisNetClose(c); + return REDIS_ERR; + } + return REDIS_OK; +} + #define __MAX_MSEC (((LONG_MAX) - 999) / 1000) static int redisContextTimeoutMsec(redisContext *c, long *result) diff --git a/net.h b/net.h index 9f43283a5..e15d46264 100644 --- a/net.h +++ b/net.h @@ -52,5 +52,6 @@ int redisKeepAlive(redisContext *c, int interval); int redisCheckConnectDone(redisContext *c, int *completed); int redisSetTcpNoDelay(redisContext *c); +int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout); #endif From 995fc03071bc778b6bf6df0c57955aa662c0dc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20S=C3=B6derqvist?= Date: Thu, 25 May 2023 17:26:45 +0200 Subject: [PATCH 2/2] Documentation for redisSetTcpUserTimeout and some more undocumented functions Documentation for redisReconnect() and the setters of socket options: * redisKeepAlive() * redisEnableKeepAliveWithInterval() * redisSetTcpUserTimeout() --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12865e7c9..74364b411 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,8 @@ REDIS_OPTIONS_SET_PRIVDATA(&opt, myPrivData, myPrivDataDtor); opt->options |= REDIS_OPT_PREFER_IPV4; ``` +If a connection is lost, `int redisReconnect(redisContext *c)` can be used to restore the connection using the same endpoint and options as the given context. + ### Configurable redisOptions flags There are several flags you may set in the `redisOptions` struct to change default behavior. You can specify the flags via the `redisOptions->options` member. @@ -138,6 +140,36 @@ There are several flags you may set in the `redisOptions` struct to change defau *Note: A `redisContext` is not thread-safe.* +### Other configuration using socket options + +The following socket options are applied directly to the underlying socket. +The values are not stored in the `redisContext`, so they are not automatically applied when reconnecting using `redisReconnect()`. +These functions return `REDIS_OK` on success. +On failure, `REDIS_ERR` is returned and the underlying connection is closed. + +To configure these for an asyncronous context (see *Asynchronous API* below), use `ac->c` to get the redisContext out of an asyncRedisContext. + +```C +int redisEnableKeepAlive(redisContext *c); +int redisEnableKeepAliveWithInterval(redisContext *c, int interval); +``` + +Enables TCP keepalive by setting the following socket options (with some variations depending on OS): + +* `SO_KEEPALIVE`; +* `TCP_KEEPALIVE` or `TCP_KEEPIDLE`, value configurable using the `interval` parameter, default 15 seconds; +* `TCP_KEEPINTVL` set to 1/3 of `interval`; +* `TCP_KEEPCNT` set to 3. + +```C +int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout); +``` + +Set the `TCP_USER_TIMEOUT` Linux-specific socket option which is as described in the `tcp` man page: + +> When the value is greater than 0, it specifies the maximum amount of time in milliseconds that trans mitted data may remain unacknowledged before TCP will forcibly close the corresponding connection and return ETIMEDOUT to the application. +> If the option value is specified as 0, TCP will use the system default. + ### Sending commands There are several ways to issue commands to Redis. The first that will be introduced is @@ -451,7 +483,6 @@ void appOnDisconnect(redisAsyncContext *c, int status) } ``` - ### Sending commands and their callbacks In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.