Fix WebUI startup blocking on slow gateway routes
This commit is contained in:
@@ -9,9 +9,11 @@ Also houses shared HTTP utility functions used by both this module and
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import mimetypes
|
||||
import re
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -76,6 +78,8 @@ from nanobot.webui.thread_disk import delete_webui_thread
|
||||
from nanobot.webui.transcript import build_webui_thread_response
|
||||
from nanobot.webui.workspaces import WebUIWorkspaceController
|
||||
|
||||
_SLOW_WEBUI_HTTP_LOG_MS = 1_000
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.cron.service import CronService
|
||||
@@ -192,7 +196,21 @@ class GatewayHTTPHandler:
|
||||
async def dispatch(self, connection: Any, request: WsRequest) -> Any | None:
|
||||
"""Route an HTTP request. Returns Response or None."""
|
||||
got, _ = _parse_request_path(request.path)
|
||||
started = time.perf_counter()
|
||||
response: Any | None = None
|
||||
|
||||
try:
|
||||
response = await self._dispatch_resolved(connection, request, got)
|
||||
return response
|
||||
finally:
|
||||
self._log_slow_http(got, response, started)
|
||||
|
||||
async def _dispatch_resolved(
|
||||
self,
|
||||
connection: Any,
|
||||
request: WsRequest,
|
||||
got: str,
|
||||
) -> Any | None:
|
||||
# Token issue endpoint
|
||||
if self.config.token_issue_path:
|
||||
issue_expected = _normalize_config_path(self.config.token_issue_path)
|
||||
@@ -209,7 +227,7 @@ class GatewayHTTPHandler:
|
||||
return response
|
||||
|
||||
# Session routes
|
||||
response = self._dispatch_session_routes(request, got)
|
||||
response = await self._dispatch_session_routes(request, got)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
@@ -219,7 +237,7 @@ class GatewayHTTPHandler:
|
||||
return response
|
||||
|
||||
# Misc routes
|
||||
response = self._dispatch_misc_routes(connection, request, got)
|
||||
response = await self._dispatch_misc_routes(connection, request, got)
|
||||
if response is not None:
|
||||
return response
|
||||
|
||||
@@ -235,6 +253,20 @@ class GatewayHTTPHandler:
|
||||
|
||||
return connection.respond(404, "Not Found")
|
||||
|
||||
def _log_slow_http(self, path: str, response: Any | None, started: float) -> None:
|
||||
elapsed_ms = int((time.perf_counter() - started) * 1000)
|
||||
if elapsed_ms < _SLOW_WEBUI_HTTP_LOG_MS:
|
||||
return
|
||||
if not (path.startswith("/api/") or path == "/webui/bootstrap"):
|
||||
return
|
||||
status = getattr(response, "status_code", None)
|
||||
self._log.warning(
|
||||
"slow webui http route path={} status={} duration_ms={}",
|
||||
path,
|
||||
status if status is not None else "none",
|
||||
elapsed_ms,
|
||||
)
|
||||
|
||||
# -- Token issue --------------------------------------------------------
|
||||
|
||||
def _handle_token_issue(self, connection: Any, request: Any) -> Any:
|
||||
@@ -302,7 +334,7 @@ class GatewayHTTPHandler:
|
||||
|
||||
# -- Session routes -----------------------------------------------------
|
||||
|
||||
def _dispatch_session_routes(self, request: WsRequest, got: str) -> Response | None:
|
||||
async def _dispatch_session_routes(self, request: WsRequest, got: str) -> Response | None:
|
||||
m = re.match(r"^/api/sessions/([^/]+)/messages$", got)
|
||||
if m:
|
||||
return self._handle_session_messages(request, m.group(1))
|
||||
@@ -325,11 +357,16 @@ class GatewayHTTPHandler:
|
||||
|
||||
return None
|
||||
|
||||
def _handle_sessions_list(self, request: WsRequest) -> Response:
|
||||
async def _handle_sessions_list(self, request: WsRequest) -> Response:
|
||||
if not self.check_api_token(request):
|
||||
return _http_error(401, "Unauthorized")
|
||||
if self.session_manager is None:
|
||||
return _http_error(503, "session manager unavailable")
|
||||
payload = await asyncio.to_thread(self._sessions_list_payload)
|
||||
return _http_json_response(payload)
|
||||
|
||||
def _sessions_list_payload(self) -> dict[str, Any]:
|
||||
assert self.session_manager is not None
|
||||
sessions = list_webui_sessions(self.session_manager)
|
||||
from nanobot.session.webui_turns import websocket_turn_wall_started_at
|
||||
|
||||
@@ -346,7 +383,7 @@ class GatewayHTTPHandler:
|
||||
scope = self.workspaces.scope_for_session_key(key)
|
||||
row["workspace_scope"] = scope.payload()
|
||||
cleaned.append(row)
|
||||
return _http_json_response({"sessions": cleaned})
|
||||
return {"sessions": cleaned}
|
||||
|
||||
def _handle_session_messages(self, request: WsRequest, key: str) -> Response:
|
||||
if not self.check_api_token(request):
|
||||
@@ -496,11 +533,11 @@ class GatewayHTTPHandler:
|
||||
|
||||
# -- Misc routes --------------------------------------------------------
|
||||
|
||||
def _dispatch_misc_routes(
|
||||
async def _dispatch_misc_routes(
|
||||
self, connection: Any, request: WsRequest, got: str
|
||||
) -> Response | None:
|
||||
if got == "/api/sessions":
|
||||
return self._handle_sessions_list(request)
|
||||
return await self._handle_sessions_list(request)
|
||||
if got == "/api/commands":
|
||||
return self._handle_commands(request)
|
||||
if got == "/api/workspaces":
|
||||
|
||||
Reference in New Issue
Block a user