perf(host): open daemon readiness polls tight, then back off (#5190)

The host-online, runner-online and Claude-terminal-ready waits all polled
on a flat 0.5s cadence. Those waits gate every native-harness launch and
usually resolve on the first probe or two — a warm host is already online,
a fresh runner connects in a second or two — so a flat cadence spends up
to a full interval doing nothing after the thing is already ready.

Replace the fixed sleep with `daemon_poll_intervals()`: open at 0.1s, grow
geometrically, hold at the existing 0.5s for the long tail. Fast launches
notice readiness sooner without a long wait hammering the server.

`DAEMON_POLL_INTERVAL_S` keeps its name and value as the steady-state cap,
so `connect.py`'s runner-exit watcher still matches it; its comment now
notes the client's opening probes are tighter.

Signed-off-by: harry-yao_data <harry.yao@databricks.com>
Co-authored-by: harry-yao_data <harry.yao@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
This commit is contained in:
Harry Yao
2026-08-21 23:21:41 -10:00
committed by GitHub
parent 07afe65150
commit 72b4469d72
4 changed files with 64 additions and 12 deletions
+3 -2
View File
@@ -108,7 +108,7 @@ from omnigent.claude_native_state import (
from omnigent.conversation_browser import conversation_url, open_conversation_link_if_enabled
from omnigent.entities.session_resources import terminal_resource_id
from omnigent.host.daemon_launch import (
DAEMON_POLL_INTERVAL_S,
daemon_poll_intervals,
error_text,
launch_or_reuse_daemon_runner,
open_daemon_client,
@@ -3824,11 +3824,12 @@ async def _wait_for_claude_terminal_ready(
:raises click.ClickException: If no terminal appears in time.
"""
deadline = asyncio.get_event_loop().time() + timeout_s
intervals = daemon_poll_intervals()
while asyncio.get_event_loop().time() < deadline:
terminal_id = await _find_running_claude_terminal(client, session_id)
if terminal_id is not None:
return terminal_id
await asyncio.sleep(DAEMON_POLL_INTERVAL_S)
await asyncio.sleep(next(intervals))
raise click.ClickException(
f"The runner did not create the Claude terminal for {session_id!r} "
f"within {timeout_s:.0f}s."
+2 -2
View File
@@ -185,8 +185,8 @@ _LOG_TAIL_MAX_BYTES = 4096
# the error summary above it remains visible.
_LOG_TAIL_MAX_LINES = 15
# Poll cadence for the per-runner exit watcher. 0.5s matches the
# client's online-poll cadence (daemon_launch.DAEMON_POLL_INTERVAL_S),
# Poll cadence for the per-runner exit watcher. 0.5s matches the client's
# steady-state online-poll cadence (daemon_launch.DAEMON_POLL_INTERVAL_S),
# so a crashed runner is reported within about one client poll.
_RUNNER_WATCH_INTERVAL_S = 0.5
+29 -4
View File
@@ -15,6 +15,7 @@ from __future__ import annotations
import asyncio
import json
from collections.abc import Iterator
import click
import httpx
@@ -22,10 +23,32 @@ import httpx
from omnigent.claude_native_bridge import url_component
from omnigent.process_logging import display_log_path, process_log_dir
# Poll cadence while waiting for a daemon-spawned runner to connect its
# tunnel or for a resource to appear.
# Steady-state poll cadence while waiting for a daemon-spawned runner to
# connect its tunnel or for a resource to appear.
DAEMON_POLL_INTERVAL_S = 0.5
# Readiness usually lands on the first probe or two, so open tight and ease
# off to the steady cadence for the long tail.
DAEMON_POLL_INITIAL_INTERVAL_S = 0.1
DAEMON_POLL_BACKOFF_FACTOR = 1.5
def daemon_poll_intervals() -> Iterator[float]:
"""
Yield successive sleeps between daemon readiness probes.
Starts at :data:`DAEMON_POLL_INITIAL_INTERVAL_S` and grows
geometrically to :data:`DAEMON_POLL_INTERVAL_S`, then holds there.
Infinite: callers stop on their own deadline.
:returns: Iterator of sleep durations in seconds, e.g.
``0.1, 0.15, 0.225, ... 0.5, 0.5``.
"""
interval = DAEMON_POLL_INITIAL_INTERVAL_S
while True:
yield interval
interval = min(interval * DAEMON_POLL_BACKOFF_FACTOR, DAEMON_POLL_INTERVAL_S)
def _json_body(resp: httpx.Response) -> dict[str, object]:
"""Decode a host/runner status response body, tolerating non-JSON.
@@ -120,6 +143,7 @@ async def wait_for_host_online(
:raises click.ClickException: If the host is not online in time.
"""
deadline = asyncio.get_event_loop().time() + timeout_s
intervals = daemon_poll_intervals()
last_error: httpx.TransportError | None = None
while asyncio.get_event_loop().time() < deadline:
try:
@@ -129,7 +153,7 @@ async def wait_for_host_online(
else:
if resp.status_code == 200 and _json_body(resp).get("status") == "online":
return
await asyncio.sleep(DAEMON_POLL_INTERVAL_S)
await asyncio.sleep(next(intervals))
message = (
f"The connect daemon for host {host_id!r} did not come online within {timeout_s:.0f}s."
)
@@ -180,6 +204,7 @@ async def wait_for_runner_online(
does not connect in time.
"""
deadline = asyncio.get_event_loop().time() + timeout_s
intervals = daemon_poll_intervals()
last_error: httpx.TransportError | None = None
while asyncio.get_event_loop().time() < deadline:
try:
@@ -199,7 +224,7 @@ async def wait_for_runner_online(
raise click.ClickException(
f"Runner {runner_id!r} failed to start: {exit_error}"
)
await asyncio.sleep(DAEMON_POLL_INTERVAL_S)
await asyncio.sleep(next(intervals))
message = f"Runner {runner_id!r} did not connect within {timeout_s:.0f}s."
if last_error is not None:
message += f" Last connection error: {last_error!r}."