mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 00:03:01 +03:00
refactor(gateway): scope foreground registration
This commit is contained in:
@@ -954,8 +954,5 @@ def _run_gateway(
|
|||||||
finally:
|
finally:
|
||||||
restore_shutdown_handlers()
|
restore_shutdown_handlers()
|
||||||
|
|
||||||
gateway_runtime.claim_current_process(gateway_start_options)
|
with gateway_runtime.foreground_instance(gateway_start_options):
|
||||||
try:
|
|
||||||
asyncio.run(run())
|
asyncio.run(run())
|
||||||
finally:
|
|
||||||
gateway_runtime.release_current_process()
|
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from contextlib import contextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, cast
|
from typing import Any, Generator, cast
|
||||||
|
|
||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
|
|
||||||
@@ -103,8 +104,16 @@ class GatewayRuntime(ManagedProcessRuntime[ProcessStartOptions]):
|
|||||||
def _build_child_command(self, options: ProcessStartOptions) -> list[str]:
|
def _build_child_command(self, options: ProcessStartOptions) -> list[str]:
|
||||||
return build_gateway_command(self.python_executable, options)
|
return build_gateway_command(self.python_executable, options)
|
||||||
|
|
||||||
def claim_current_process(self, options: ProcessStartOptions) -> None:
|
@contextmanager
|
||||||
"""Publish the running foreground gateway for local client discovery."""
|
def foreground_instance(self, options: ProcessStartOptions) -> Generator[None]:
|
||||||
|
"""Publish this foreground gateway while it is available to local clients."""
|
||||||
|
self._claim_current_process(options)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self._release_current_process()
|
||||||
|
|
||||||
|
def _claim_current_process(self, options: ProcessStartOptions) -> None:
|
||||||
with self._lifecycle_lock():
|
with self._lifecycle_lock():
|
||||||
state = self._read_state() or {}
|
state = self._read_state() or {}
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
@@ -123,8 +132,7 @@ class GatewayRuntime(ManagedProcessRuntime[ProcessStartOptions]):
|
|||||||
)
|
)
|
||||||
self._write_state(state)
|
self._write_state(state)
|
||||||
|
|
||||||
def release_current_process(self) -> None:
|
def _release_current_process(self) -> None:
|
||||||
"""Remove this foreground gateway's state without touching a replacement."""
|
|
||||||
with self._lifecycle_lock():
|
with self._lifecycle_lock():
|
||||||
state = self._read_state()
|
state = self._read_state()
|
||||||
if state and self._record_matches_process(state, os.getpid()):
|
if state and self._record_matches_process(state, os.getpid()):
|
||||||
|
|||||||
@@ -118,16 +118,13 @@ def test_foreground_gateway_claim_is_discoverable_and_released(tmp_path, monkeyp
|
|||||||
config_path="/tmp/config.json",
|
config_path="/tmp/config.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
runtime.claim_current_process(options)
|
with runtime.foreground_instance(options):
|
||||||
|
|
||||||
status = runtime.status()
|
status = runtime.status()
|
||||||
assert status.running is True
|
assert status.running is True
|
||||||
assert status.pid == os.getpid()
|
assert status.pid == os.getpid()
|
||||||
assert status.port == 18790
|
assert status.port == 18790
|
||||||
assert status.command == tuple(runtime._build_child_command(options))
|
assert status.command == tuple(runtime._build_child_command(options))
|
||||||
|
|
||||||
runtime.release_current_process()
|
|
||||||
|
|
||||||
assert runtime.status().running is False
|
assert runtime.status().running is False
|
||||||
assert not runtime.paths.state_path.exists()
|
assert not runtime.paths.state_path.exists()
|
||||||
|
|
||||||
@@ -135,17 +132,26 @@ def test_foreground_gateway_claim_is_discoverable_and_released(tmp_path, monkeyp
|
|||||||
def test_foreground_gateway_release_preserves_a_replacement_state(tmp_path, monkeypatch):
|
def test_foreground_gateway_release_preserves_a_replacement_state(tmp_path, monkeypatch):
|
||||||
runtime = GatewayRuntime(paths=_paths(tmp_path), platform_name="Darwin")
|
runtime = GatewayRuntime(paths=_paths(tmp_path), platform_name="Darwin")
|
||||||
monkeypatch.setattr(runtime, "_process_identity", lambda pid: pid)
|
monkeypatch.setattr(runtime, "_process_identity", lambda pid: pid)
|
||||||
runtime.claim_current_process(GatewayStartOptions(port=18790))
|
with runtime.foreground_instance(GatewayStartOptions(port=18790)):
|
||||||
replacement = json.loads(runtime.paths.state_path.read_text(encoding="utf-8"))
|
replacement = json.loads(runtime.paths.state_path.read_text(encoding="utf-8"))
|
||||||
replacement["pid"] = os.getpid() + 1
|
replacement["pid"] = os.getpid() + 1
|
||||||
replacement["identity"] = replacement["pid"]
|
replacement["identity"] = replacement["pid"]
|
||||||
runtime.paths.state_path.write_text(json.dumps(replacement), encoding="utf-8")
|
runtime.paths.state_path.write_text(json.dumps(replacement), encoding="utf-8")
|
||||||
|
|
||||||
runtime.release_current_process()
|
|
||||||
|
|
||||||
assert runtime.paths.state_path.exists()
|
assert runtime.paths.state_path.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_foreground_gateway_clears_its_state_after_an_error(tmp_path, monkeypatch):
|
||||||
|
runtime = GatewayRuntime(paths=_paths(tmp_path), platform_name="Darwin")
|
||||||
|
monkeypatch.setattr(runtime, "_process_identity", lambda pid: pid)
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="startup failed"):
|
||||||
|
with runtime.foreground_instance(GatewayStartOptions(port=18790)):
|
||||||
|
raise RuntimeError("startup failed")
|
||||||
|
|
||||||
|
assert not runtime.paths.state_path.exists()
|
||||||
|
|
||||||
|
|
||||||
def test_stop_reaps_an_owned_child_without_consuming_the_shutdown_timeout(
|
def test_stop_reaps_an_owned_child_without_consuming_the_shutdown_timeout(
|
||||||
tmp_path,
|
tmp_path,
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
|
|||||||
Reference in New Issue
Block a user