From 2fa559f075e8ad82f06d3ef73b6ab186a73c8c7d Mon Sep 17 00:00:00 2001 From: Francis CLAIRICIA-ROSE-CLAIRE-JOSEPHINE Date: Thu, 28 Sep 2023 10:06:22 +0200 Subject: [PATCH 1/2] [FIX] Fixed CancelScope's delayed cancellation system --- src/easynetwork_asyncio/tasks.py | 7 ++- .../test_backend/test_asyncio_backend.py | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/easynetwork_asyncio/tasks.py b/src/easynetwork_asyncio/tasks.py index 6041f15d..80d74fbf 100644 --- a/src/easynetwork_asyncio/tasks.py +++ b/src/easynetwork_asyncio/tasks.py @@ -145,7 +145,6 @@ class CancelScope(AbstractCancelScope): "__state", "__cancel_called", "__cancelled_caught", - "__task_cancelling", "__deadline", "__timeout_handle", "__delayed_cancellation_on_enter", @@ -160,7 +159,6 @@ def __init__(self, *, deadline: float = math.inf) -> None: self.__state: _ScopeState = _ScopeState.CREATED self.__cancel_called: bool = False self.__cancelled_caught: bool = False - self.__task_cancelling: int = 0 self.__deadline: float = math.inf self.__timeout_handle: asyncio.TimerHandle | None = None self.reschedule(deadline) @@ -180,7 +178,6 @@ def __enter__(self) -> Self: raise RuntimeError("CancelScope entered twice") self.__host_task = current_task = TaskUtils.current_asyncio_task() - self.__task_cancelling = current_task.cancelling() current_task_scope = self.__current_task_scope_dict if current_task not in current_task_scope: @@ -218,13 +215,15 @@ def __exit__(self, exc_type: type[BaseException] | None, exc_val: BaseException if self.__cancel_called: task_cancelling = host_task.uncancel() if isinstance(exc_val, asyncio.CancelledError): - self.__cancelled_caught = task_cancelling <= self.__task_cancelling or self.__cancellation_id() in exc_val.args + self.__cancelled_caught = self.__cancellation_id() in exc_val.args delayed_task_cancel = self.__delayed_task_cancel_dict.get(host_task, None) if delayed_task_cancel is not None and delayed_task_cancel.message == self.__cancellation_id(): del self.__delayed_task_cancel_dict[host_task] delayed_task_cancel.handle.cancel() + delayed_task_cancel = None + if delayed_task_cancel is None: for cancel_scope in self._inner_to_outer_task_scopes(host_task): if cancel_scope.__cancel_called: self._reschedule_delayed_task_cancel(host_task, cancel_scope.__cancellation_id()) diff --git a/tests/functional_test/test_async/test_backend/test_asyncio_backend.py b/tests/functional_test/test_async/test_backend/test_asyncio_backend.py index 7c40e2eb..848ab302 100644 --- a/tests/functional_test/test_async/test_backend/test_asyncio_backend.py +++ b/tests/functional_test/test_async/test_backend/test_asyncio_backend.py @@ -1240,6 +1240,58 @@ async def coroutine() -> None: await event_loop.create_task(coroutine()) + @pytest.mark.xfail(raises=asyncio.CancelledError, reason="Task.cancel() cannot be erased", strict=True) + async def test____cancel_shielded_coroutine____scope_cancellation_edge_case_3( + self, + event_loop: asyncio.AbstractEventLoop, + backend: AsyncIOBackend, + ) -> None: + async def coroutine() -> None: + current_task = asyncio.current_task() + assert current_task is not None + + with backend.move_on_after(0) as inner_scope: + pass + + await backend.coro_yield() + + assert inner_scope.cancel_called() + + assert not inner_scope.cancelled_caught() + + await event_loop.create_task(coroutine()) + + async def test____cancel_shielded_coroutine____scope_cancellation_edge_case_4( + self, + event_loop: asyncio.AbstractEventLoop, + backend: AsyncIOBackend, + ) -> None: + async def coroutine() -> None: + current_task = asyncio.current_task() + assert current_task is not None + + outer_scope = backend.open_cancel_scope() + inner_scope = backend.open_cancel_scope() + with outer_scope: + outer_scope.cancel() + + await backend.ignore_cancellation(backend.sleep(0.1)) + + with inner_scope: + inner_scope.cancel() + + await backend.coro_yield() + + await backend.coro_yield() + + assert outer_scope.cancel_called() + assert inner_scope.cancel_called() + + assert not inner_scope.cancelled_caught() + assert outer_scope.cancelled_caught() + + await event_loop.create_task(coroutine()) + async def test____ignore_cancellation____do_not_reschedule_if_inner_task_cancelled_itself( self, event_loop: asyncio.AbstractEventLoop, From 27cc11dd9e22ff8fc4f812e69727ab60bca13b42 Mon Sep 17 00:00:00 2001 From: Francis CLAIRICIA-ROSE-CLAIRE-JOSEPHINE Date: Thu, 28 Sep 2023 10:50:38 +0200 Subject: [PATCH 2/2] [FIX] Added missing test --- .../test_backend/test_asyncio_backend.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/functional_test/test_async/test_backend/test_asyncio_backend.py b/tests/functional_test/test_async/test_backend/test_asyncio_backend.py index 848ab302..b90b8450 100644 --- a/tests/functional_test/test_async/test_backend/test_asyncio_backend.py +++ b/tests/functional_test/test_async/test_backend/test_asyncio_backend.py @@ -1292,6 +1292,39 @@ async def coroutine() -> None: await event_loop.create_task(coroutine()) + async def test____cancel_shielded_coroutine____scope_cancellation_edge_case_5( + self, + event_loop: asyncio.AbstractEventLoop, + backend: AsyncIOBackend, + ) -> None: + async def coroutine() -> None: + current_task = asyncio.current_task() + assert current_task is not None + + outer_scope = backend.open_cancel_scope() + inner_scope = backend.open_cancel_scope() + with outer_scope: + with inner_scope: + inner_scope.cancel() + + await backend.cancel_shielded_coro_yield() + + outer_scope.cancel() + + await backend.cancel_shielded_coro_yield() + + await backend.coro_yield() + + await backend.coro_yield() + + assert outer_scope.cancel_called() + assert inner_scope.cancel_called() + + assert not inner_scope.cancelled_caught() + assert outer_scope.cancelled_caught() + + await event_loop.create_task(coroutine()) + async def test____ignore_cancellation____do_not_reschedule_if_inner_task_cancelled_itself( self, event_loop: asyncio.AbstractEventLoop,