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.
32 lines
946 B
Python
32 lines
946 B
Python
"""Tearing a node down must not swallow a cancellation meant for the caller."""
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from fluksio.flow.nodes import DelayNode
|
|
|
|
|
|
def test_stop_cron_lets_the_callers_cancellation_through():
|
|
async def stubborn() -> None:
|
|
"""A loop whose shutdown does not answer the first cancellation."""
|
|
try:
|
|
await asyncio.sleep(3600)
|
|
except asyncio.CancelledError:
|
|
await asyncio.sleep(3600)
|
|
|
|
async def scenario() -> None:
|
|
node = DelayNode(params={"cron": "* * * * *"})
|
|
node._stop_cron = asyncio.Event()
|
|
node._cron_task = asyncio.create_task(stubborn())
|
|
|
|
stopping = asyncio.create_task(node.stop_cron())
|
|
await asyncio.sleep(0.05) # let it reach the await on the cron task
|
|
stopping.cancel()
|
|
with pytest.raises(asyncio.CancelledError):
|
|
await stopping
|
|
|
|
node._cron_task.cancel()
|
|
|
|
asyncio.run(scenario())
|