fix(gateway): handle lifecycle edge cases

This commit is contained in:
chengyongru
2026-06-24 10:29:08 +08:00
committed by Xubin Ren
parent 7826f8f89c
commit bc1df49201
4 changed files with 63 additions and 17 deletions
+13 -13
View File
@@ -178,7 +178,8 @@ class GatewayRuntime:
self._clear_state()
return RuntimeResult(False, "gateway_state_stale", self.status(reason="stale_state"))
self._terminate(status.pid, timeout_s=timeout_s)
if not self._terminate(status.pid, timeout_s=timeout_s):
return RuntimeResult(False, "gateway_stop_timeout", self.status(reason="stop_timeout"))
self._clear_state()
return RuntimeResult(True, "gateway_stopped", self.status(reason="stopped"))
@@ -263,13 +264,12 @@ class GatewayRuntime:
return {"creationflags": flags}
return {"start_new_session": True}
def _terminate(self, pid: int, *, timeout_s: int) -> None:
def _terminate(self, pid: int, *, timeout_s: int) -> bool:
if self.platform_name == "Windows":
self._terminate_windows(pid, timeout_s=timeout_s)
else:
self._terminate_posix(pid, timeout_s=timeout_s)
return self._terminate_windows(pid, timeout_s=timeout_s)
return self._terminate_posix(pid, timeout_s=timeout_s)
def _terminate_posix(self, pid: int, *, timeout_s: int) -> None:
def _terminate_posix(self, pid: int, *, timeout_s: int) -> bool:
try:
pgid = os.getpgid(pid)
except OSError:
@@ -280,28 +280,28 @@ class GatewayRuntime:
else:
os.kill(pid, signal.SIGTERM)
except ProcessLookupError:
return
return True
if self._wait_for_exit(pid, timeout_s):
return
return True
with suppress(ProcessLookupError):
if pgid is not None:
os.killpg(pgid, signal.SIGKILL)
else:
os.kill(pid, signal.SIGKILL)
self._wait_for_exit(pid, 2)
return self._wait_for_exit(pid, 2)
def _terminate_windows(self, pid: int, *, timeout_s: int) -> None:
def _terminate_windows(self, pid: int, *, timeout_s: int) -> bool:
ctrl_break = getattr(signal, "CTRL_BREAK_EVENT", None)
if ctrl_break is not None:
with suppress(ProcessLookupError):
os.kill(pid, ctrl_break)
if self._wait_for_exit(pid, timeout_s):
return
return True
self._subprocess_run(["taskkill", "/PID", str(pid), "/T"], check=False)
if self._wait_for_exit(pid, 2):
return
return True
self._subprocess_run(["taskkill", "/PID", str(pid), "/T", "/F"], check=False)
self._wait_for_exit(pid, 2)
return self._wait_for_exit(pid, 2)
def _wait_for_exit(self, pid: int, timeout_s: int | float) -> bool:
deadline = time.monotonic() + max(float(timeout_s), 0.0)
+3 -3
View File
@@ -141,7 +141,7 @@ class GatewayServiceInstaller:
"Label": label,
"ProgramArguments": build_gateway_command(options.python_executable, options.start),
"WorkingDirectory": _working_directory_text(options.start),
"RunAtLoad": bool(options.start_now),
"RunAtLoad": bool(options.enable),
"KeepAlive": {"SuccessfulExit": False},
"StandardOutPath": str(stdout_path),
"StandardErrorPath": str(stderr_path),
@@ -149,7 +149,7 @@ class GatewayServiceInstaller:
content = plistlib.dumps(payload, sort_keys=False).decode("utf-8")
domain = _launchd_domain()
commands: list[tuple[str, ...]] = []
if options.enable or options.start_now:
if options.start_now:
commands.append(("launchctl", "bootstrap", domain, str(path)))
if options.enable:
commands.append(("launchctl", "enable", f"{domain}/{label}"))
@@ -162,7 +162,7 @@ class GatewayServiceInstaller:
path.parent.mkdir(parents=True, exist_ok=True)
stdout_path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
if options.enable or options.start_now:
if options.start_now:
self._run_best_effort(("launchctl", "bootout", domain, str(path)))
for command_args in commands:
self._subprocess_run(list(command_args), check=True)