Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit04adf2d

Browse files
kristjanvalurgvanrossumAlexWaygood
authored
gh-102780: Fix uncancel() call in asyncio timeouts (#102815)
Also use `raise TimeOut from <CancelledError instance>` so that the CancelledError is setin the `__cause__` field rather than in the `__context__` field.Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent1ca3155 commit04adf2d

File tree

4 files changed

+50
-6
lines changed

4 files changed

+50
-6
lines changed

‎Doc/library/asyncio-task.rst‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,13 +300,17 @@ in the task at the next opportunity.
300300
It is recommended that coroutines use ``try/finally`` blocks to robustly
301301
perform clean-up logic. In case:exc:`asyncio.CancelledError`
302302
is explicitly caught, it should generally be propagated when
303-
clean-up is complete. Most code can safely ignore:exc:`asyncio.CancelledError`.
303+
clean-up is complete.:exc:`asyncio.CancelledError` directly subclasses
304+
:exc:`BaseException` so most code will not need to be aware of it.
304305

305306
The asyncio components that enable structured concurrency, like
306307
:class:`asyncio.TaskGroup` and:func:`asyncio.timeout`,
307308
are implemented using cancellation internally and might misbehave if
308309
a coroutine swallows:exc:`asyncio.CancelledError`. Similarly, user code
309-
should not call:meth:`uncancel <asyncio.Task.uncancel>`.
310+
should not generally call:meth:`uncancel <asyncio.Task.uncancel>`.
311+
However, in cases when suppressing:exc:`asyncio.CancelledError` is
312+
truly desired, it is necessary to also call ``uncancel()`` to completely
313+
remove the cancellation state.
310314

311315
.. _taskgroups:
312316

@@ -1148,7 +1152,9 @@ Task Object
11481152
Therefore, unlike:meth:`Future.cancel`,:meth:`Task.cancel` does
11491153
not guarantee that the Task will be cancelled, although
11501154
suppressing cancellation completely is not common and is actively
1151-
discouraged.
1155+
discouraged. Should the coroutine nevertheless decide to suppress
1156+
the cancellation, it needs to call:meth:`Task.uncancel` in addition
1157+
to catching the exception.
11521158

11531159
..versionchanged::3.9
11541160
Added the *msg* parameter.
@@ -1238,6 +1244,10 @@ Task Object
12381244
with:meth:`uncancel`.:class:`TaskGroup` context managers use
12391245
:func:`uncancel` in a similar fashion.
12401246

1247+
If end-user code is, for some reason, suppresing cancellation by
1248+
catching:exc:`CancelledError`, it needs to call this method to remove
1249+
the cancellation state.
1250+
12411251
..method::cancelling()
12421252

12431253
Return the number of pending cancellation requests to this Task, i.e.,

‎Lib/asyncio/timeouts.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def __repr__(self) -> str:
8484
asyncdef__aenter__(self)->"Timeout":
8585
self._state=_State.ENTERED
8686
self._task=tasks.current_task()
87+
self._cancelling=self._task.cancelling()
8788
ifself._taskisNone:
8889
raiseRuntimeError("Timeout should be used inside a task")
8990
self.reschedule(self._when)
@@ -104,10 +105,10 @@ async def __aexit__(
104105
ifself._stateis_State.EXPIRING:
105106
self._state=_State.EXPIRED
106107

107-
ifself._task.uncancel()==0andexc_typeisexceptions.CancelledError:
108-
# Since there are nooutstanding cancel requests, we're
108+
ifself._task.uncancel()<=self._cancellingandexc_typeisexceptions.CancelledError:
109+
# Since there are nonew cancel requests, we're
109110
# handling this.
110-
raiseTimeoutError
111+
raiseTimeoutErrorfromexc_val
111112
elifself._stateis_State.ENTERED:
112113
self._state=_State.EXITED
113114

‎Lib/test/test_asyncio/test_timeouts.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,36 @@ async def test_nested_timeout_in_finally(self):
247247
asyncwithasyncio.timeout(0.01):
248248
awaitasyncio.sleep(10)
249249

250+
asyncdeftest_timeout_after_cancellation(self):
251+
try:
252+
asyncio.current_task().cancel()
253+
awaitasyncio.sleep(1)# work which will be cancelled
254+
exceptasyncio.CancelledError:
255+
pass
256+
finally:
257+
withself.assertRaises(TimeoutError):
258+
asyncwithasyncio.timeout(0.0):
259+
awaitasyncio.sleep(1)# some cleanup
260+
261+
asyncdeftest_cancel_in_timeout_after_cancellation(self):
262+
try:
263+
asyncio.current_task().cancel()
264+
awaitasyncio.sleep(1)# work which will be cancelled
265+
exceptasyncio.CancelledError:
266+
pass
267+
finally:
268+
withself.assertRaises(asyncio.CancelledError):
269+
asyncwithasyncio.timeout(1.0):
270+
asyncio.current_task().cancel()
271+
awaitasyncio.sleep(2)# some cleanup
272+
273+
asyncdeftest_timeout_exception_cause (self):
274+
withself.assertRaises(asyncio.TimeoutError)asexc:
275+
asyncwithasyncio.timeout(0):
276+
awaitasyncio.sleep(1)
277+
cause=exc.exception.__cause__
278+
assertisinstance(cause,asyncio.CancelledError)
279+
250280

251281
if__name__=='__main__':
252282
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The:class:`asyncio.Timeout` context manager now works reliably even when performing cleanup due
2+
to task cancellation. Previously it could raise a
3+
:exc:`~asyncio.CancelledError` instead of an:exc:`~asyncio.TimeoutError` in such cases.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp