fix(webui): detach without waiting for gateway shutdown

This commit is contained in:
chengyongru
2026-08-19 16:34:07 +08:00
committed by chengyongru
parent 242f417370
commit 2e9c9781e6
2 changed files with 31 additions and 7 deletions
+2 -6
View File
@@ -326,10 +326,7 @@ def webui(
raise typer.Exit(1) from exc raise typer.Exit(1) from exc
return return
finally: finally:
if lease.release(): lease.release(wait_for_stop=False)
console.print(
"[dim]Last local client exited; the on-demand gateway was stopped.[/dim]"
)
gateway_port_taken = gateway_ready or _tcp_endpoint_reachable( gateway_port_taken = gateway_ready or _tcp_endpoint_reachable(
_host_for_local_browser(runtime_config.gateway.host), _host_for_local_browser(runtime_config.gateway.host),
@@ -372,5 +369,4 @@ def webui(
_open_webui_browser(webui_url) _open_webui_browser(webui_url)
_attach_to_background_gateway(runtime) _attach_to_background_gateway(runtime)
finally: finally:
if lease.release(): lease.release(wait_for_stop=False)
console.print("[dim]Last local client exited; the on-demand gateway was stopped.[/dim]")
+29 -1
View File
@@ -1949,6 +1949,28 @@ def _patch_gateway_ports_free(monkeypatch) -> None:
) )
def _record_gateway_lease_release(monkeypatch, captured: dict[str, object]) -> None:
from nanobot.gateway import GatewayClientLease
original_release = GatewayClientLease.release
def record_release(
lease: GatewayClientLease,
*,
timeout_s: int = 20,
wait_for_stop: bool = True,
) -> bool:
captured["lease_release_wait_for_stop"] = wait_for_stop
captured["dev_running_at_release"] = captured.get("dev_running")
return original_release(
lease,
timeout_s=timeout_s,
wait_for_stop=wait_for_stop,
)
monkeypatch.setattr(GatewayClientLease, "release", record_release)
def _patch_webui_managed_gateway( def _patch_webui_managed_gateway(
monkeypatch, monkeypatch,
seen: dict[str, object] | None = None, seen: dict[str, object] | None = None,
@@ -1957,6 +1979,7 @@ def _patch_webui_managed_gateway(
from nanobot.gateway import GatewayStatus, RuntimeResult from nanobot.gateway import GatewayStatus, RuntimeResult
captured = seen if seen is not None else {} captured = seen if seen is not None else {}
_record_gateway_lease_release(monkeypatch, captured)
class _FakeRuntime: class _FakeRuntime:
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
@@ -2219,7 +2242,8 @@ def test_webui_yes_creates_config_and_enables_local_websocket(
assert "bootstrap secret was generated" in compact_output assert "bootstrap secret was generated" in compact_output
assert "channels.websocket.tokenIssueSecret" in compact_output assert "channels.websocket.tokenIssueSecret" in compact_output
assert "rerun without --no-open" in compact_output assert "rerun without --no-open" in compact_output
assert "Last local client exited; the on-demand gateway was stopped" in compact_output assert seen["lease_release_wait_for_stop"] is False
assert "stop_timeout" not in seen
def test_webui_background_points_to_the_single_persistent_gateway_command( def test_webui_background_points_to_the_single_persistent_gateway_command(
@@ -2312,6 +2336,8 @@ def test_webui_dev_starts_vite_sidecar_and_gateway(monkeypatch, tmp_path: Path)
assert seen["attach_kwargs"] == {"poll_hook": seen["dev_server"].ensure_running} assert seen["attach_kwargs"] == {"poll_hook": seen["dev_server"].ensure_running}
assert seen["opened_url"] == browser_url assert seen["opened_url"] == browser_url
assert seen["dev_running"] is False assert seen["dev_running"] is False
assert seen["dev_running_at_release"] is False
assert seen["lease_release_wait_for_stop"] is False
assert "WebUI dev: http://127.0.0.1:5173/#/?bootstrapSecret=<redacted>" in re.sub( assert "WebUI dev: http://127.0.0.1:5173/#/?bootstrapSecret=<redacted>" in re.sub(
r"\s+", " ", _strip_ansi(result.stdout) r"\s+", " ", _strip_ansi(result.stdout)
) )
@@ -2509,6 +2535,7 @@ def test_webui_foreground_attaches_to_existing_managed_gateway(monkeypatch, tmp_
config_file = tmp_path / "config.json" config_file = tmp_path / "config.json"
config_file.write_text("{}") config_file.write_text("{}")
seen: dict[str, object] = {} seen: dict[str, object] = {}
_record_gateway_lease_release(monkeypatch, seen)
_patch_webui_provider_ready(monkeypatch) _patch_webui_provider_ready(monkeypatch)
monkeypatch.setattr("nanobot.cli.webui.sync_workspace_templates", lambda _path: None) monkeypatch.setattr("nanobot.cli.webui.sync_workspace_templates", lambda _path: None)
monkeypatch.setattr("nanobot.cli.webui._gateway_health_ready", lambda *_args, **_kwargs: True) monkeypatch.setattr("nanobot.cli.webui._gateway_health_ready", lambda *_args, **_kwargs: True)
@@ -2571,6 +2598,7 @@ def test_webui_foreground_attaches_to_existing_managed_gateway(monkeypatch, tmp_
fragment = parsed.fragment.removeprefix("/?") fragment = parsed.fragment.removeprefix("/?")
assert parse_qs(fragment).get("bootstrapSecret") assert parse_qs(fragment).get("bootstrapSecret")
assert seen["open_kwargs"] == {"wait": False} assert seen["open_kwargs"] == {"wait": False}
assert seen["lease_release_wait_for_stop"] is False
def test_attach_to_background_gateway_detaches_on_ctrl_c(capsys) -> None: def test_attach_to_background_gateway_detaches_on_ctrl_c(capsys) -> None: