diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index aec11a8a3f..dfe5f70c00 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -507,6 +507,15 @@ try: except ValueError: WEBSOCKET_SERVER_PING_INTERVAL = 25 +WEBSOCKET_HEARTBEAT_INTERVAL = os.getenv('WEBSOCKET_HEARTBEAT_INTERVAL', '') +if WEBSOCKET_HEARTBEAT_INTERVAL == '': + WEBSOCKET_HEARTBEAT_INTERVAL = None +else: + try: + WEBSOCKET_HEARTBEAT_INTERVAL = min(max(int(WEBSOCKET_HEARTBEAT_INTERVAL), 5), 90) + except ValueError: + WEBSOCKET_HEARTBEAT_INTERVAL = 30 + WEBSOCKET_EVENT_CALLER_TIMEOUT = os.getenv('WEBSOCKET_EVENT_CALLER_TIMEOUT', '') if WEBSOCKET_EVENT_CALLER_TIMEOUT == '': diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index f440621493..9d4476652c 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -111,6 +111,7 @@ from open_webui.env import ( SAFE_MODE, SCIM_TOKEN, VERSION, + WEBSOCKET_HEARTBEAT_INTERVAL, # Admin Account Runtime Creation WEBUI_ADMIN_EMAIL, WEBUI_ADMIN_NAME, @@ -2297,6 +2298,11 @@ async def get_app_config(request: Request): 'enable_signup': config.get('ui.enable_signup'), 'enable_login_form': config.get('ui.enable_login_form'), 'enable_websocket': ENABLE_WEBSOCKET_SUPPORT, + **( + {'websocket_heartbeat_interval': WEBSOCKET_HEARTBEAT_INTERVAL} + if WEBSOCKET_HEARTBEAT_INTERVAL is not None + else {} + ), # --- Authenticated: only consumed by logged-in frontend --- **( { diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index bf6f1deb33..a1ccfe0be4 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -16,6 +16,7 @@ from open_webui.env import ( GLOBAL_LOG_LEVEL, REDIS_KEY_PREFIX, WEBSOCKET_EVENT_CALLER_TIMEOUT, + WEBSOCKET_HEARTBEAT_INTERVAL, WEBSOCKET_MANAGER, WEBSOCKET_REDIS_CLUSTER, WEBSOCKET_REDIS_LOCK_TIMEOUT, @@ -102,7 +103,11 @@ else: # Timeout duration in seconds TIMEOUT_DURATION = 3 -SESSION_POOL_TIMEOUT = 120 # seconds without heartbeat before session is reaped +SESSION_POOL_TIMEOUT = ( + max(WEBSOCKET_HEARTBEAT_INTERVAL * 4, 120) + if WEBSOCKET_HEARTBEAT_INTERVAL is not None + else 120 +) # Dictionary to maintain the user pool diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 6b2401b4c4..9109e675d8 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -355,6 +355,7 @@ type Config = { enable_version_update_check: boolean; enable_pyodide_file_persistence?: boolean; folder_max_file_count?: number; + websocket_heartbeat_interval?: number | null; }; oauth: { providers: { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index ecb2c50f45..d16d9ddbd5 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -213,13 +213,15 @@ } } - // Send heartbeat every 30 seconds - heartbeatInterval = setInterval(() => { - if (_socket.connected) { - console.log('Sending heartbeat'); - _socket.emit('heartbeat', {}); - } - }, 30000); + heartbeatInterval = setInterval( + () => { + if (_socket.connected) { + console.log('Sending heartbeat'); + _socket.emit('heartbeat', {}); + } + }, + ($config?.features?.websocket_heartbeat_interval ?? 30) * 1000 + ); if (deploymentId !== null) { WEBUI_DEPLOYMENT_ID.set(deploymentId);