diff --git a/helpers/ui_server.py b/helpers/ui_server.py index 6cf9b7bb9..cbf14a054 100644 --- a/helpers/ui_server.py +++ b/helpers/ui_server.py @@ -37,6 +37,19 @@ from helpers.ws_manager import WsManager, set_shared_ws_manager UPLOAD_LIMIT_BYTES = 5 * 1024 * 1024 * 1024 +SOCKETIO_PING_INTERVAL_SECONDS = 45 +SOCKETIO_PING_TIMEOUT_SECONDS = 120 + + +def _positive_int_env(name: str, default: int) -> int: + raw_value = os.getenv(name) + if raw_value is None: + return default + try: + value = int(raw_value) + except (TypeError, ValueError): + return default + return value if value > 0 else default def configure_process_environment() -> None: @@ -85,8 +98,14 @@ class UiServerRuntime: cors_allowed_origins=lambda _origin, environ: validate_ws_origin(environ)[0], logger=False, engineio_logger=False, - ping_interval=25, - ping_timeout=20, + ping_interval=_positive_int_env( + "A0_SOCKETIO_PING_INTERVAL_SECONDS", + SOCKETIO_PING_INTERVAL_SECONDS, + ), + ping_timeout=_positive_int_env( + "A0_SOCKETIO_PING_TIMEOUT_SECONDS", + SOCKETIO_PING_TIMEOUT_SECONDS, + ), max_http_buffer_size=50 * 1024 * 1024, ) diff --git a/helpers/ui_server.py.dox.md b/helpers/ui_server.py.dox.md index 5f634884e..4c993c84d 100644 --- a/helpers/ui_server.py.dox.md +++ b/helpers/ui_server.py.dox.md @@ -26,19 +26,21 @@ - `async serve_plugin_asset(self, plugin_name, asset_path)` - `async serve_extension_asset(self, asset_path)` - Top-level functions: +- `_positive_int_env(name: str, default: int) -> int` - `configure_process_environment() -> None` -- Notable constants/configuration names: `UPLOAD_LIMIT_BYTES`. +- Notable constants/configuration names: `UPLOAD_LIMIT_BYTES`, `SOCKETIO_PING_INTERVAL_SECONDS`, `SOCKETIO_PING_TIMEOUT_SECONDS`, `A0_SOCKETIO_PING_INTERVAL_SECONDS`, `A0_SOCKETIO_PING_TIMEOUT_SECONDS`. ## Runtime Contracts - Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together. - Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change. +- Socket.IO heartbeat defaults are intentionally longer than Engine.IO's short defaults so CLI sessions survive long prompt/context work; environment overrides must remain positive integers and fall back to source defaults when invalid. - Observed side-effect areas: filesystem reads, network calls, subprocess/runtime control, WebSocket state, plugin state, settings/state persistence, secret handling. - Imported dependency areas include: `asyncio`, `dataclasses`, `datetime`, `flask`, `helpers`, `helpers.api`, `helpers.extension`, `helpers.files`, `helpers.print_style`, `helpers.server_startup`, `helpers.ws`, `helpers.ws_manager`, `logging`, `os`, `secrets`, `socketio`. ## Key Concepts -- Important called helpers/classes observed in the source: `logging.getLogger.setLevel`, `Localization.get.apply_process_timezone`, `field`, `Flask`, `threading.RLock`, `socketio.AsyncServer`, `WsManager`, `set_shared_ws_manager`, `cls`, `server_runtime.refresh_runtime_settings`, `settings_helper.get_settings`, `settings_helper.set_runtime_settings_snapshot`, `self.ws_manager.set_server_restart_broadcast`, `UiRouteHandlers`, `self.webapp.add_url_rule`, `register_api_route`, `register_ws_namespace`, `files.read_file`, `render_template_string`, `session.pop`. +- Important called helpers/classes observed in the source: `logging.getLogger.setLevel`, `Localization.get.apply_process_timezone`, `_positive_int_env`, `field`, `Flask`, `threading.RLock`, `socketio.AsyncServer`, `WsManager`, `set_shared_ws_manager`, `cls`, `server_runtime.refresh_runtime_settings`, `settings_helper.get_settings`, `settings_helper.set_runtime_settings_snapshot`, `self.ws_manager.set_server_restart_broadcast`, `UiRouteHandlers`, `self.webapp.add_url_rule`, `register_api_route`, `register_ws_namespace`, `files.read_file`, `render_template_string`, `session.pop`. - Keep request/response, tool, or helper semantics documented here at the same time as source changes. ## Work Guidance diff --git a/tests/test_run_ui_config.py b/tests/test_run_ui_config.py index e2d3a4c59..befd5e62a 100644 --- a/tests/test_run_ui_config.py +++ b/tests/test_run_ui_config.py @@ -5,12 +5,23 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) -import run_ui +from helpers.ui_server import UiServerRuntime def test_socketio_engine_configuration_defaults(): - server = run_ui.socketio_server.eio + server = UiServerRuntime.create().socketio_server.eio - assert server.ping_interval == 25 - assert server.ping_timeout == 20 + assert server.ping_interval == 45 + assert server.ping_timeout == 120 + assert server.max_http_buffer_size == 50 * 1024 * 1024 + + +def test_socketio_engine_configuration_uses_env_overrides(monkeypatch): + monkeypatch.setenv("A0_SOCKETIO_PING_INTERVAL_SECONDS", "30") + monkeypatch.setenv("A0_SOCKETIO_PING_TIMEOUT_SECONDS", "90") + + server = UiServerRuntime.create().socketio_server.eio + + assert server.ping_interval == 30 + assert server.ping_timeout == 90 assert server.max_http_buffer_size == 50 * 1024 * 1024