feat(daemon): implement reload, fix PID race condition
- PluginSupervisor.reload(): cancels all running plugin tasks, resets restart counters, and re-creates them with fresh coroutines - IPC reload command now calls supervisor.reload() instead of being a stub - run_foreground(): wrap PID file acquisition in try/except PidFileError to produce a clean error if two daemon starts race on the PID file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+29
-9
@@ -68,6 +68,22 @@ class PluginSupervisor:
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
async def reload(self) -> None:
|
||||
"""Cancel all running tasks and restart them with fresh coroutines."""
|
||||
for record in self._records:
|
||||
if record.task and not record.task.done():
|
||||
record.task.cancel()
|
||||
try:
|
||||
await record.task
|
||||
except (asyncio.CancelledError, Exception):
|
||||
pass
|
||||
record.restart_count = 0
|
||||
record.last_error = None
|
||||
record.task = asyncio.create_task(
|
||||
self._supervise(record), name=record.name
|
||||
)
|
||||
_log.info("Reloaded %d plugin task(s).", len(self._records))
|
||||
|
||||
def status(self) -> list[dict]:
|
||||
return [
|
||||
{
|
||||
@@ -131,8 +147,8 @@ def _make_ipc_handler(supervisor: PluginSupervisor):
|
||||
supervisor.request_shutdown()
|
||||
return {"ok": True, "data": {}}
|
||||
case "reload":
|
||||
_log.info("Reload requested via IPC.")
|
||||
return {"ok": True, "data": {}}
|
||||
await supervisor.reload()
|
||||
return {"ok": True, "data": {"tasks_reloaded": len(supervisor._records)}}
|
||||
case _:
|
||||
return {"ok": False, "data": {"error": f"unknown command: {cmd}"}}
|
||||
|
||||
@@ -196,13 +212,17 @@ def run_foreground() -> None:
|
||||
|
||||
_start_time = time.monotonic()
|
||||
|
||||
with pid_file:
|
||||
_log.info("Pyra daemon starting (PID %d).", os.getpid())
|
||||
try:
|
||||
asyncio.run(_run_daemon(cfg, supervisor))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
_log.info("Pyra daemon stopped.")
|
||||
try:
|
||||
with pid_file:
|
||||
_log.info("Pyra daemon starting (PID %d).", os.getpid())
|
||||
try:
|
||||
asyncio.run(_run_daemon(cfg, supervisor))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
_log.info("Pyra daemon stopped.")
|
||||
except PidFileError as exc:
|
||||
_log.error("Could not acquire PID file: %s", exc)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ── Background spawn (pyra daemon start) ─────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user