From 717ec47623e62cabd5a8088023600e0f7c69dac0 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 28 Jul 2022 21:17:54 +0530 Subject: [PATCH] GH-95097: fix `asyncio.run` for tasks without `uncancel` method (GH-95211) Co-authored-by: Thomas Grainger (cherry picked from commit 54f48844d18bc6fb98849f15a2fc08f92ad240ea) Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> --- Lib/asyncio/runners.py | 9 ++-- Lib/test/test_asyncio/test_runners.py | 51 ++++++++++++++++++- ...2-07-24-18-00-42.gh-issue-95097.lu5qNf.rst | 1 + 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-07-24-18-00-42.gh-issue-95097.lu5qNf.rst diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py index c58b52ed862e8e..a19a7f99af064d 100644 --- a/Lib/asyncio/runners.py +++ b/Lib/asyncio/runners.py @@ -119,10 +119,11 @@ def run(self, coro, *, context=None): events.set_event_loop(self._loop) return self._loop.run_until_complete(task) except exceptions.CancelledError: - if self._interrupt_count > 0 and task.uncancel() == 0: - raise KeyboardInterrupt() - else: - raise # CancelledError + if self._interrupt_count > 0: + uncancel = getattr(task, "uncancel", None) + if uncancel is not None and uncancel() == 0: + raise KeyboardInterrupt() + raise # CancelledError finally: if (sigint_handler is not None and signal.getsignal(signal.SIGINT) is sigint_handler diff --git a/Lib/test/test_asyncio/test_runners.py b/Lib/test/test_asyncio/test_runners.py index a27942c4f53eba..5e1db2357e75f1 100644 --- a/Lib/test/test_asyncio/test_runners.py +++ b/Lib/test/test_asyncio/test_runners.py @@ -6,10 +6,9 @@ import signal import threading import unittest - +from test.test_asyncio import utils as test_utils from unittest import mock from unittest.mock import patch -from test.test_asyncio import utils as test_utils def tearDownModule(): @@ -211,6 +210,54 @@ async def main(): asyncio.run(main()) self.assertTrue(policy.set_event_loop.called) + def test_asyncio_run_without_uncancel(self): + # See https://github.com/python/cpython/issues/95097 + class Task: + def __init__(self, loop, coro, **kwargs): + self._task = asyncio.Task(coro, loop=loop, **kwargs) + + def cancel(self, *args, **kwargs): + return self._task.cancel(*args, **kwargs) + + def add_done_callback(self, *args, **kwargs): + return self._task.add_done_callback(*args, **kwargs) + + def remove_done_callback(self, *args, **kwargs): + return self._task.remove_done_callback(*args, **kwargs) + + @property + def _asyncio_future_blocking(self): + return self._task._asyncio_future_blocking + + def result(self, *args, **kwargs): + return self._task.result(*args, **kwargs) + + def done(self, *args, **kwargs): + return self._task.done(*args, **kwargs) + + def cancelled(self, *args, **kwargs): + return self._task.cancelled(*args, **kwargs) + + def exception(self, *args, **kwargs): + return self._task.exception(*args, **kwargs) + + def get_loop(self, *args, **kwargs): + return self._task.get_loop(*args, **kwargs) + + + async def main(): + interrupt_self() + await asyncio.Event().wait() + + def new_event_loop(): + loop = self.new_loop() + loop.set_task_factory(Task) + return loop + + asyncio.set_event_loop_policy(TestPolicy(new_event_loop)) + with self.assertRaises(asyncio.CancelledError): + asyncio.run(main()) + class RunnerTests(BaseTest): diff --git a/Misc/NEWS.d/next/Library/2022-07-24-18-00-42.gh-issue-95097.lu5qNf.rst b/Misc/NEWS.d/next/Library/2022-07-24-18-00-42.gh-issue-95097.lu5qNf.rst new file mode 100644 index 00000000000000..2840f057a9c1dc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-24-18-00-42.gh-issue-95097.lu5qNf.rst @@ -0,0 +1 @@ +Fix :func:`asyncio.run` for :class:`asyncio.Task` implementations without :meth:`~asyncio.Task.uncancel` method. Patch by Kumar Aditya.