Skip to content

Commit db7f122

Browse files
Classic298claude
andauthored
fix(redis): add opt-in TCP socket keepalive on all client connections (open-webui#23571)
Introduces REDIS_SOCKET_KEEPALIVE and wires socket_keepalive=True through to every Redis client created by get_redis_connection (plain, cluster and sentinel paths, sync and async). When enabled, the kernel sends TCP keepalive probes on idle connections so half-closed sockets (e.g. after a silent firewall/LB reset or a NIC flap) are detected before the next command lands on them and the request never sees a "Connection reset by peer" error. Defaults to off so existing deployments see no behavioural change. Operators who want the protection set REDIS_SOCKET_KEEPALIVE=true in their environment. Co-authored-by: Claude <noreply@anthropic.com>
1 parent aacf95c commit db7f122

2 files changed

Lines changed: 20 additions & 0 deletions

File tree

backend/open_webui/env.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,15 @@ def parse_section(section):
426426
except ValueError:
427427
REDIS_SOCKET_CONNECT_TIMEOUT = None
428428

429+
# Whether to enable TCP SO_KEEPALIVE on Redis client sockets. Opt-in:
430+
# defaults to off so behavior is unchanged for existing deployments. When
431+
# enabled, the kernel sends TCP keepalive probes on idle connections so
432+
# half-closed sockets (e.g. after a silent firewall/LB reset or a NIC
433+
# flap) are detected before the next command lands on them.
434+
REDIS_SOCKET_KEEPALIVE = (
435+
os.environ.get('REDIS_SOCKET_KEEPALIVE', 'False').lower() == 'true'
436+
)
437+
429438
REDIS_RECONNECT_DELAY = os.environ.get('REDIS_RECONNECT_DELAY', '')
430439

431440
if REDIS_RECONNECT_DELAY == '':

backend/open_webui/utils/redis.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from open_webui.env import (
1111
REDIS_CLUSTER,
1212
REDIS_SOCKET_CONNECT_TIMEOUT,
13+
REDIS_SOCKET_KEEPALIVE,
1314
REDIS_SENTINEL_HOSTS,
1415
REDIS_SENTINEL_MAX_RETRY_COUNT,
1516
REDIS_SENTINEL_PORT,
@@ -197,6 +198,10 @@ def get_redis_connection(
197198
else {}
198199
)
199200

201+
keepalive_kwargs = (
202+
{'socket_keepalive': True} if REDIS_SOCKET_KEEPALIVE else {}
203+
)
204+
200205
if async_mode:
201206
import redis.asyncio as redis
202207

@@ -211,6 +216,7 @@ def get_redis_connection(
211216
password=redis_config['password'],
212217
decode_responses=decode_responses,
213218
socket_connect_timeout=REDIS_SOCKET_CONNECT_TIMEOUT,
219+
**keepalive_kwargs,
214220
)
215221
connection = SentinelRedisProxy(
216222
sentinel,
@@ -224,12 +230,14 @@ def get_redis_connection(
224230
redis_url,
225231
decode_responses=decode_responses,
226232
**connect_timeout_kwargs,
233+
**keepalive_kwargs,
227234
)
228235
elif redis_url:
229236
connection = redis.from_url(
230237
redis_url,
231238
decode_responses=decode_responses,
232239
**connect_timeout_kwargs,
240+
**keepalive_kwargs,
233241
)
234242
else:
235243
import redis
@@ -244,6 +252,7 @@ def get_redis_connection(
244252
password=redis_config['password'],
245253
decode_responses=decode_responses,
246254
socket_connect_timeout=REDIS_SOCKET_CONNECT_TIMEOUT,
255+
**keepalive_kwargs,
247256
)
248257
connection = SentinelRedisProxy(
249258
sentinel,
@@ -257,12 +266,14 @@ def get_redis_connection(
257266
redis_url,
258267
decode_responses=decode_responses,
259268
**connect_timeout_kwargs,
269+
**keepalive_kwargs,
260270
)
261271
elif redis_url:
262272
connection = redis.Redis.from_url(
263273
redis_url,
264274
decode_responses=decode_responses,
265275
**connect_timeout_kwargs,
276+
**keepalive_kwargs,
266277
)
267278

268279
_CONNECTION_CACHE[cache_key] = connection

0 commit comments

Comments
 (0)