Let a connector's teardown cancellation through too
ConnectorNode.stop cancelled its poll task and then caught CancelledError
around the await — the fourth site of the trap 93e4527 closed elsewhere,
swallowing a cancellation aimed at whoever asked for the teardown. It now
calls the shared Node._cancel_task, which keeps retrieving whatever the
loop raised on its way out, as the old `except (CancelledError, Exception)`
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1moruzue2kTJd3uVisgNk
This commit is contained in:
@@ -161,11 +161,7 @@ class ConnectorNode(Node):
|
|||||||
return
|
return
|
||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
if self._poll_task is not None:
|
if self._poll_task is not None:
|
||||||
self._poll_task.cancel()
|
await self._cancel_task(self._poll_task)
|
||||||
try:
|
|
||||||
await self._poll_task
|
|
||||||
except (asyncio.CancelledError, Exception): # noqa: B014 - shutting down
|
|
||||||
pass
|
|
||||||
self._poll_task = None
|
self._poll_task = None
|
||||||
self._stop_event = None
|
self._stop_event = None
|
||||||
self._last_published = {}
|
self._last_published = {}
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ import asyncio
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from fluksio.flow.connector import ConnectorNode
|
||||||
from fluksio.flow.nodes import DelayNode
|
from fluksio.flow.nodes import DelayNode
|
||||||
|
|
||||||
|
|
||||||
def test_stop_cron_lets_the_callers_cancellation_through():
|
async def stubborn() -> None:
|
||||||
async def stubborn() -> None:
|
"""A loop whose shutdown does not answer the first cancellation."""
|
||||||
"""A loop whose shutdown does not answer the first cancellation."""
|
try:
|
||||||
try:
|
await asyncio.sleep(3600)
|
||||||
await asyncio.sleep(3600)
|
except asyncio.CancelledError:
|
||||||
except asyncio.CancelledError:
|
await asyncio.sleep(3600)
|
||||||
await asyncio.sleep(3600)
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_stop_cron_lets_the_callers_cancellation_through():
|
||||||
async def scenario() -> None:
|
async def scenario() -> None:
|
||||||
node = DelayNode(params={"cron": "* * * * *"})
|
node = DelayNode(params={"cron": "* * * * *"})
|
||||||
node._stop_cron = asyncio.Event()
|
node._stop_cron = asyncio.Event()
|
||||||
@@ -29,3 +31,20 @@ def test_stop_cron_lets_the_callers_cancellation_through():
|
|||||||
node._cron_task.cancel()
|
node._cron_task.cancel()
|
||||||
|
|
||||||
asyncio.run(scenario())
|
asyncio.run(scenario())
|
||||||
|
|
||||||
|
|
||||||
|
def test_connector_stop_lets_the_callers_cancellation_through():
|
||||||
|
async def scenario() -> None:
|
||||||
|
node = ConnectorNode()
|
||||||
|
node._stop_event = asyncio.Event()
|
||||||
|
node._poll_task = asyncio.create_task(stubborn())
|
||||||
|
|
||||||
|
stopping = asyncio.create_task(node.stop())
|
||||||
|
await asyncio.sleep(0.05) # let it reach the await on the poll task
|
||||||
|
stopping.cancel()
|
||||||
|
with pytest.raises(asyncio.CancelledError):
|
||||||
|
await stopping
|
||||||
|
|
||||||
|
node._poll_task.cancel()
|
||||||
|
|
||||||
|
asyncio.run(scenario())
|
||||||
|
|||||||
Reference in New Issue
Block a user