8000 Possible asyncio.Task cancellation fix. · QuantumTim/cpython@b6da21b · GitHub
[go: up one dir, main page]

Skip to content

Commit b6da21b

Browse files
committed
Possible asyncio.Task cancellation fix.
- Ensures CancelledError can't be raised at the expense of leaking other values/results. - Comes at the expense of being able to (easily) abort cancellations. - You can still catch and ignore CancelledError, but it will keep re-appearing.
1 parent b949845 commit b6da21b

File tree

1 file changed

+17
-10
lines changed

1 file changed

+17
-10
lines changed

Lib/asyncio/tasks.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,25 +201,25 @@ def cancel(self, msg=None):
201201
self._log_traceback = False
202202
if self.done():
203203
return False
204+
# Once set, we keep trying to cancel the Task at every
205+
# safe opportunity. There's currently no way to stop this,
206+
# but maybe we could add CancelledError.abort() or something
207+
# for tasks to explicitly stop cancellation.
208+
self._must_cancel = True
204209
if self._fut_waiter is not None:
205210
if self._fut_waiter.cancel(msg=msg):
206211
# Leave self._fut_waiter; it may be a Task that
207212
# catches and ignores the cancellation so we may have
208213
# to cancel it again later.
209214
return True
210215
# It must be the case that self.__step is already scheduled.
211-
self._must_cancel = True
212216
self._cancel_message = msg
213217
return True
214218

215219
def __step(self, exc=None):
216220
if self.done():
217221
raise exceptions.InvalidStateError(
218222
f'_step(): already done: {self!r}, {exc!r}')
219-
if self._must_cancel:
220-
if not isinstance(exc, exceptions.CancelledError):
221-
exc = self._make_cancelled_error()
222-
self._must_cancel = False
223223
coro = self._coro
224224
self._fut_waiter = None
225225

@@ -234,11 +234,13 @@ def __step(self, exc=None):
234234
result = coro.throw(exc)
235235
except StopIteration as exc:
236236
if self._must_cancel:
237-
# Task is cancelled right before coro stops.
237+
# We were cancelled, but we're done, so we just pretend
238+
# that the cancellation came in too late and the result
239+
# was already ready. If the calling Task was the source
240+
# of the cancellation, then its own _must_cancel will
241+
# still be set, so it will still keep trying to cancel.
238242
self._must_cancel = False
239-
super().cancel(msg=self._cancel_message)
240-
else:
241-
super().set_result(exc.value)
243+
super().set_result(exc.value)
242244
except exceptions.CancelledError as exc:
243245
# Save the original exception so we can chain it later.
244246
self._cancelled_exc = exc
@@ -272,7 +274,12 @@ def __step(self, exc=None):
272274
if self._must_cancel:
273275
if self._fut_waiter.cancel(
274276
msg=self._cancel_message):
275-
self._must_cancel = False
277+
# Even if we cancelled the future, we
278+
# leave _must_cancel set because the
279+
# future might racily return a result
280+
# at the same time as the cancellation,
281+
# so we'll need to retry cancellation.
282+
pass
276283
else:
277284
3D35 new_exc = RuntimeError(
278285
f'yield was used instead of yield from '

0 commit comments

Comments
 (0)
0