@
fix(gateway): self-heal state file PID on server startup After /restart on Windows, the gateway process gets a new PID (os.execv creates a new process on Windows), but the state file at run/gateway.<suffix>.json still contains the old PID from the initial background spawn. Nothing rewrites it, leaving state inconsistent. Add GatewayRuntime.refresh_state_pid() — a classmethod that reads the existing state file, updates the PID and identity to the current process, and writes atomically. Call it early in _run_gateway() so the state file is always correct regardless of how the process was started (initial spawn, os.execv, or subprocess.Popen). On POSIX os.execv preserves the PID, so this is a no-op in normal operation there, but still beneficial after any unusual restart path. Fixes #4511 @
This commit is contained in:
@@ -1306,6 +1306,17 @@ def _run_gateway(
|
||||
raise typer.Exit(1) from exc
|
||||
session_manager = SessionManager(config.workspace_path)
|
||||
|
||||
# Self-heal the gateway state file with the current PID after any restart.
|
||||
from nanobot.gateway.runtime import GatewayRuntime, GatewayRuntimePaths
|
||||
GatewayRuntime.refresh_state_pid(
|
||||
paths=GatewayRuntimePaths.for_instance(
|
||||
workspace=str(config.workspace_path)
|
||||
if not is_default_workspace(config.workspace_path)
|
||||
else None,
|
||||
config_path=config.config_path,
|
||||
)
|
||||
)
|
||||
|
||||
# Preserve existing single-workspace installs, but keep custom workspaces clean.
|
||||
if is_default_workspace(config.workspace_path):
|
||||
_migrate_cron_store(config)
|
||||
|
||||
@@ -127,6 +127,25 @@ class GatewayRuntime:
|
||||
self._subprocess_run = subprocess_run
|
||||
self._sleep = sleep
|
||||
|
||||
@classmethod
|
||||
def refresh_state_pid(cls, *, paths: GatewayRuntimePaths) -> None:
|
||||
"""Update the PID in an existing state file to ``os.getpid()``.
|
||||
|
||||
Called early in gateway server startup so the state file self-heals
|
||||
after any restart, regardless of platform or restart mechanism.
|
||||
"""
|
||||
if not paths.state_path.exists():
|
||||
return
|
||||
try:
|
||||
state = json.loads(paths.state_path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return
|
||||
state["pid"] = os.getpid()
|
||||
rt = cls(paths=paths)
|
||||
state["identity"] = rt._process_identity(os.getpid())
|
||||
state["started_at"] = _utc_now()
|
||||
rt._write_state(state)
|
||||
|
||||
def start_background(self, options: GatewayStartOptions) -> RuntimeResult:
|
||||
"""Start gateway as a detached background process."""
|
||||
current = self.status()
|
||||
|
||||
Reference in New Issue
Block a user