Skip to content

Commit 54f4884

Browse files
GH-95097: fix asyncio.run for tasks without uncancel method (#95211)
Co-authored-by: Thomas Grainger <[email protected]>
1 parent bceb197 commit 54f4884

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

Lib/asyncio/runners.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,11 @@ def run(self, coro, *, context=None):
118118
events.set_event_loop(self._loop)
119119
return self._loop.run_until_complete(task)
120120
except exceptions.CancelledError:
121-
if self._interrupt_count > 0 and task.uncancel() == 0:
122-
raise KeyboardInterrupt()
123-
else:
124-
raise # CancelledError
121+
if self._interrupt_count > 0:
122+
uncancel = getattr(task, "uncancel", None)
123+
if uncancel is not None and uncancel() == 0:
124+
raise KeyboardInterrupt()
125+
raise # CancelledError
125126
finally:
126127
if (sigint_handler is not None
127128
and signal.getsignal(signal.SIGINT) is sigint_handler

Lib/test/test_asyncio/test_runners.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import signal
66
import threading
77
import unittest
8-
8+
from test.test_asyncio import utils as test_utils
99
from unittest import mock
1010
from unittest.mock import patch
11-
from test.test_asyncio import utils as test_utils
1211

1312

1413
def tearDownModule():
@@ -210,6 +209,54 @@ async def main():
210209
asyncio.run(main())
211210
self.assertTrue(policy.set_event_loop.called)
212211

212+
def test_asyncio_run_without_uncancel(self):
213+
# See https://github.com/python/cpython/issues/95097
214+
class Task:
215+
def __init__(self, loop, coro, **kwargs):
216+
self._task = asyncio.Task(coro, loop=loop, **kwargs)
217+
218+
def cancel(self, *args, **kwargs):
219+
return self._task.cancel(*args, **kwargs)
220+
221+
def add_done_callback(self, *args, **kwargs):
222+
return self._task.add_done_callback(*args, **kwargs)
223+
224+
def remove_done_callback(self, *args, **kwargs):
225+
return self._task.remove_done_callback(*args, **kwargs)
226+
227+
@property
228+
def _asyncio_future_blocking(self):
229+
return self._task._asyncio_future_blocking
230+
231+
def result(self, *args, **kwargs):
232+
return self._task.result(*args, **kwargs)
233+
234+
def done(self, *args, **kwargs):
235+
return self._task.done(*args, **kwargs)
236+
237+
def cancelled(self, *args, **kwargs):
238+
return self._task.cancelled(*args, **kwargs)
239+
240+
def exception(self, *args, **kwargs):
241+
return self._task.exception(*args, **kwargs)
242+
243+
def get_loop(self, *args, **kwargs):
244+
return self._task.get_loop(*args, **kwargs)
245+
246+
247+
async def main():
248+
interrupt_self()
249+
await asyncio.Event().wait()
250+
251+
def new_event_loop():
252+
loop = self.new_loop()
253+
loop.set_task_factory(Task)
254+
return loop
255+
256+
asyncio.set_event_loop_policy(TestPolicy(new_event_loop))
257+
with self.assertRaises(asyncio.CancelledError):
258+
asyncio.run(main())
259+
213260

214261
class RunnerTests(BaseTest):
215262

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :func:`asyncio.run` for :class:`asyncio.Task` implementations without :meth:`~asyncio.Task.uncancel` method. Patch by Kumar Aditya.

0 commit comments

Comments
 (0)