Compare commits

...

1 Commits

Author SHA1 Message Date
Yuan Tang 2d42210cb4 perf(runner): cache terminal is_alive() probe with short TTL
E2E Tests / setup (push) Has been skipped
E2E Tests / E2E Tests (shard ${{ matrix.shard_id }}/${{ matrix.num_shards }}) (push) Has been skipped
Integration Tests / setup (push) Has been skipped
Integration Tests / Integration (${{ matrix.name }}) (push) Has been skipped
E2E UI Tests / gate (push) Failing after 1s
E2E Tests / gate (push) Failing after 0s
E2E UI Tests / setup (push) Has been skipped
E2E UI Tests / E2E UI Tests (shard ${{ matrix.shard_id }}/${{ matrix.num_shards }}) (push) Has been skipped
Integration Tests / gate (push) Failing after 1s
E2E Tests / Merge Ready rerun (push) Has been cancelled
E2E UI Tests / Merge Ready rerun (push) Has been cancelled
Integration Tests / Merge Ready rerun (push) Has been cancelled
Rapid web-client polling of the terminal GET endpoint forks a
tmux has-session subprocess on every request. Add a 2-second
TTLCache so the probe runs at most once per terminal per TTL
window, while still detecting dead tmux servers promptly.
2026-06-22 13:35:52 -07:00
+24 -2
View File
@@ -22,6 +22,8 @@ from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal
from cachetools import TTLCache
from omnigent.entities.pagination import PagedList
from omnigent.entities.session_resources import (
DEFAULT_ENVIRONMENT_ID,
@@ -56,6 +58,9 @@ CURSOR_NATIVE_TERMINAL_ROLE = "cursor-native"
# (the REPL exited or crashed) instead of rejecting the attach.
OMNIGENT_REPL_TERMINAL_ROLE = "omnigent-repl"
_IS_ALIVE_CACHE_TTL_S = 2.0
_IS_ALIVE_CACHE_MAX = 256
# Diff-track idle threshold (seconds) for the claude-native agent
# terminal's status watcher. Claude Code redraws its busy line every
# ~200ms while a turn is in progress, so a poll that sees no pane change
@@ -258,6 +263,10 @@ class SessionResourceRegistry:
self._primary_envs: dict[str, OSEnvironment] = {}
self._terminal_roles: dict[tuple[str, str], str] = {}
self._terminal_lifecycles: dict[tuple[str, str], TerminalLifecycle] = {}
self._is_alive_cache: TTLCache[str, bool] = TTLCache(
maxsize=_IS_ALIVE_CACHE_MAX,
ttl=_IS_ALIVE_CACHE_TTL_S,
)
self._lock = threading.Lock()
# Optional callback ``(session_id, terminal_id) -> None`` invoked
# (on the event loop) when a terminal's pane produces output, so
@@ -443,7 +452,6 @@ class SessionResourceRegistry:
)
return get_resource_by_id(page, resource_id)
# TODO(perf): cache is_alive() if terminal poll rate becomes a problem.
async def get_terminal_resource(
self,
session_id: str,
@@ -458,6 +466,10 @@ class SessionResourceRegistry:
this method so clients do not reconnect to a stale socket that
can only print tmux's ``"no sessions"`` error.
The ``is_alive()`` subprocess probe is cached for a short TTL
so rapid polling from web clients does not fork a ``tmux
has-session`` process on every request.
:param session_id: Session/conversation identifier.
:param terminal_id: Opaque terminal resource id,
e.g. ``"terminal_claude_main"``.
@@ -474,7 +486,17 @@ class SessionResourceRegistry:
continue
if not entry.instance.running:
return None
if not await entry.instance.is_alive():
cache_key = f"{session_id}:{terminal_id}"
cached = self._is_alive_cache.get(cache_key)
if cached is not None:
return terminal_resource_view(session_id, entry) if cached else None
alive = await entry.instance.is_alive()
if alive:
self._is_alive_cache[cache_key] = True
else:
self._is_alive_cache.pop(cache_key, None)
return None
return terminal_resource_view(session_id, entry)
return None