Let a teardown's cancellation through, and reap the workers it leaves
Docs / docs (push) Successful in 23s
Playwright Tests / test-playwright (1, 2) (push) Successful in 3m12s
Playwright Tests / test-playwright (2, 2) (push) Successful in 1m49s
pre-commit / pre-commit (push) Failing after 2m16s
Test Backend / test-backend (push) Successful in 2m38s
Compose Smoke Test / test-compose (push) Successful in 32s
Playwright Tests / merge-reports (push) Successful in 1m15s
Docs / docs (push) Successful in 23s
Playwright Tests / test-playwright (1, 2) (push) Successful in 3m12s
Playwright Tests / test-playwright (2, 2) (push) Successful in 1m49s
pre-commit / pre-commit (push) Failing after 2m16s
Test Backend / test-backend (push) Successful in 2m38s
Compose Smoke Test / test-compose (push) Successful in 32s
Playwright Tests / merge-reports (push) Successful in 1m15s
Node stop paths cancelled their background task and then caught CancelledError around the await, which swallows a cancellation aimed at the caller — the trap Supervisor._cancel already documents. One shared Node._cancel_task now waits the way the supervisor does; mqtt's publisher and subscription and delay's cron call it. The api container also collected zombie python workers: orphaned when --reload replaces the process holding their handle, they reparent onto a PID 1 that reaps nothing but its own. `init: true` on the backend service.
This commit is contained in:
@@ -212,6 +212,22 @@ class Node:
|
||||
return None
|
||||
return asyncio.create_task(factory())
|
||||
|
||||
@staticmethod
|
||||
async def _cancel_task(task: asyncio.Task[None]) -> None:
|
||||
"""Stop an unsupervised loop and wait for it to be gone.
|
||||
|
||||
`wait` keeps whatever the task raises on its way out to itself, and
|
||||
lets a cancellation aimed at *this* coroutine through — the
|
||||
`except CancelledError` around `await task` it replaces swallowed that,
|
||||
which left whoever asked for the teardown unkillable. The same trap
|
||||
`Supervisor._cancel` documents.
|
||||
"""
|
||||
task.cancel()
|
||||
await asyncio.wait([task])
|
||||
if not task.cancelled():
|
||||
# Retrieved so a crash on the way out is not reported at exit.
|
||||
task.exception()
|
||||
|
||||
def report_health(self, status: str, detail: str | None = None) -> None:
|
||||
"""Say how this node's connection is doing: ok, degraded or down."""
|
||||
if self._on_health is not None:
|
||||
|
||||
Reference in New Issue
Block a user