Skip to content

Commit 959db0b

Browse files
RobLe3claude
andcommitted
[python] BUG-3 fix — silence shutdown race noise in serve()
Cancel gossip/heartbeat tasks BEFORE server.shutdown() so the background coroutines stop scheduling futures onto the event loop during interpreter teardown. Add loop.is_closed() guard + RuntimeError handler in _task() so in-flight handler threads return 503 instead of logging "cannot schedule new futures after interpreter shutdown". Closes BUG-3 from #357 E2E bug tracker. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b898883 commit 959db0b

1 file changed

Lines changed: 18 additions & 1 deletion

File tree

src/iicp_client/node.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,11 @@ def _task(self) -> None:
693693
pass # nonce check already completed above; span marks validation done
694694

695695
try:
696+
# BUG-3 guard: reject incoming tasks if the event loop is
697+
# already closed (interpreter shutdown race).
698+
if loop.is_closed():
699+
self.send_error(503, "node shutting down")
700+
return
696701
with task_execute_span(task_id, intent):
697702
result = asyncio.run_coroutine_threadsafe(handler(body), loop).result(
698703
timeout=60
@@ -709,6 +714,12 @@ def _task(self) -> None:
709714
}
710715
).encode()
711716
self._json_response(200, resp_body)
717+
except RuntimeError as exc:
718+
if "shutdown" in str(exc).lower() or "closed" in str(exc).lower():
719+
logger.debug("Handler skipped during node shutdown: %s", exc)
720+
self.send_error(503, "node shutting down")
721+
return
722+
raise
712723
except Exception as exc:
713724
latency_ms = (time.monotonic() - t0) * 1000
714725
node._metrics.observe("error", intent, qos, latency_ms)
@@ -744,9 +755,15 @@ def _json_response(self, status: int, body: bytes) -> None:
744755
try:
745756
await loop.run_in_executor(None, server.serve_forever)
746757
finally:
747-
server.shutdown()
758+
# BUG-3 fix: cancel background tasks BEFORE server.shutdown() so the
759+
# gossip/heartbeat coroutines stop scheduling futures onto the event
760+
# loop during interpreter teardown — silences the "cannot schedule new
761+
# futures after interpreter shutdown" noise on CTRL-C / normal exit.
748762
for t in bg_tasks:
749763
t.cancel()
764+
if bg_tasks:
765+
await asyncio.gather(*bg_tasks, return_exceptions=True)
766+
server.shutdown()
750767
# #343 — graceful pinhole revoke. Best-effort; failure here is
751768
# never fatal because router lease auto-expires (3600s default).
752769
self._revoke_pinhole_sync()

0 commit comments

Comments
 (0)