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

Commita9ece4a

Browse files
miss-islingtonkristjanvalurgvanrossumAlexWaygood
authored
gh-102780: Fix uncancel() call in asyncio timeouts (GH-102815)
Also use `raise TimeOut from <CancelledError instance>` so that the CancelledError is setin the `__cause__` field rather than in the `__context__` field.(cherry picked from commit04adf2d)Co-authored-by: Kristján Valur Jónsson <sweskman@gmail.com>Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parentf1b9673 commita9ece4a

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

@@ -1137,7 +1141,9 @@ Task Object
11371141
Therefore, unlike:meth:`Future.cancel`,:meth:`Task.cancel` does
11381142
not guarantee that the Task will be cancelled, although
11391143
suppressing cancellation completely is not common and is actively
1140-
discouraged.
1144+
discouraged. Should the coroutine nevertheless decide to suppress
1145+
the cancellation, it needs to call:meth:`Task.uncancel` in addition
1146+
to catching the exception.
11411147

11421148
..versionchanged::3.9
11431149
Added the *msg* parameter.
@@ -1227,6 +1233,10 @@ Task Object
12271233
with:meth:`uncancel`.:class:`TaskGroup` context managers use
12281234
:func:`uncancel` in a similar fashion.
12291235

1236+
If end-user code is, for some reason, suppresing cancellation by
1237+
catching:exc:`CancelledError`, it needs to call this method to remove
1238+
the cancellation state.
1239+
12301240
..method::cancelling()
12311241

12321242
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
@@ -248,6 +248,36 @@ async def test_nested_timeout_in_finally(self):
248248
asyncwithasyncio.timeout(0.01):
249249
awaitasyncio.sleep(10)
250250

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

252282
if__name__=='__main__':
253283
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