From b6a052fe0959dae69e16b9d74449faeb1b70dbe1 Mon Sep 17 00:00:00 2001 From: Viktor Söderqvist Date: Mon, 29 May 2023 22:25:34 +0200 Subject: Helper for setting TCP_USER_TIMEOUT socket option (#1188) * Implement redisSetTcpUserTimeout to set socket option TCP_USER_TIMEOUT * Documentation for redisSetTcpUserTimeout and some more undocumented functions Documentation for redisReconnect() and the setters of socket options: * redisKeepAlive() * redisEnableKeepAliveWithInterval() * redisSetTcpUserTimeout() --- README.md | 33 ++++++++++++++++++++++++++++++++- hiredis.c | 5 +++++ hiredis.h | 1 + net.c | 16 ++++++++++++++++ net.h | 1 + 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12865e7..74364b4 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. diff --git a/hiredis.c b/hiredis.c index 72502eb..9d8a500 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 f490059..2291d3e 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 ec96412..ccd7f16 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 9f43283..e15d462 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 -- cgit v1.2.3