diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 53737a1f2f..f612248ffe 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -426,6 +426,15 @@ try: except ValueError: REDIS_SOCKET_CONNECT_TIMEOUT = None +# Whether to enable TCP SO_KEEPALIVE on Redis client sockets. Opt-in: +# defaults to off so behavior is unchanged for existing deployments. 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. +REDIS_SOCKET_KEEPALIVE = ( + os.environ.get('REDIS_SOCKET_KEEPALIVE', 'False').lower() == 'true' +) + REDIS_RECONNECT_DELAY = os.environ.get('REDIS_RECONNECT_DELAY', '') if REDIS_RECONNECT_DELAY == '': diff --git a/backend/open_webui/utils/redis.py b/backend/open_webui/utils/redis.py index c2e5da1fae..ec1dee5e9b 100644 --- a/backend/open_webui/utils/redis.py +++ b/backend/open_webui/utils/redis.py @@ -10,6 +10,7 @@ import redis from open_webui.env import ( REDIS_CLUSTER, REDIS_SOCKET_CONNECT_TIMEOUT, + REDIS_SOCKET_KEEPALIVE, REDIS_SENTINEL_HOSTS, REDIS_SENTINEL_MAX_RETRY_COUNT, REDIS_SENTINEL_PORT, @@ -197,6 +198,10 @@ def get_redis_connection( else {} ) + keepalive_kwargs = ( + {'socket_keepalive': True} if REDIS_SOCKET_KEEPALIVE else {} + ) + if async_mode: import redis.asyncio as redis @@ -211,6 +216,7 @@ def get_redis_connection( password=redis_config['password'], decode_responses=decode_responses, socket_connect_timeout=REDIS_SOCKET_CONNECT_TIMEOUT, + **keepalive_kwargs, ) connection = SentinelRedisProxy( sentinel, @@ -224,12 +230,14 @@ def get_redis_connection( redis_url, decode_responses=decode_responses, **connect_timeout_kwargs, + **keepalive_kwargs, ) elif redis_url: connection = redis.from_url( redis_url, decode_responses=decode_responses, **connect_timeout_kwargs, + **keepalive_kwargs, ) else: import redis @@ -244,6 +252,7 @@ def get_redis_connection( password=redis_config['password'], decode_responses=decode_responses, socket_connect_timeout=REDIS_SOCKET_CONNECT_TIMEOUT, + **keepalive_kwargs, ) connection = SentinelRedisProxy( sentinel, @@ -257,12 +266,14 @@ def get_redis_connection( redis_url, decode_responses=decode_responses, **connect_timeout_kwargs, + **keepalive_kwargs, ) elif redis_url: connection = redis.Redis.from_url( redis_url, decode_responses=decode_responses, **connect_timeout_kwargs, + **keepalive_kwargs, ) _CONNECTION_CACHE[cache_key] = connection