]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-105836: Fix `asyncio.run_coroutine_threadsafe` leaving underlying cancelled...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 9 Dec 2025 14:45:50 +0000 (15:45 +0100)
committerGitHub <noreply@github.com>
Tue, 9 Dec 2025 14:45:50 +0000 (20:15 +0530)
gh-105836: Fix `asyncio.run_coroutine_threadsafe` leaving underlying cancelled asyncio task running (GH-141696)
(cherry picked from commit 14715e3a64a674629c781d4a3dd11143ba010990)

Co-authored-by: Kaisheng Xu <iaalmsimon@gmail.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Lib/asyncio/futures.py
Lib/test/test_asyncio/test_tasks.py
Misc/ACKS
Misc/NEWS.d/next/Library/2025-11-18-15-48-13.gh-issue-105836.sbUw24.rst [new file with mode: 0644]

index 51932639097bbd90afac2a04ec3eb4c61d012db1..e24e16de751b9ff63315d339f70dd17b72315f9c 100644 (file)
@@ -383,7 +383,7 @@ def _chain_future(source, destination):
 
     def _call_check_cancel(destination):
         if destination.cancelled():
-            if source_loop is None or source_loop is dest_loop:
+            if source_loop is None or source_loop is events._get_running_loop():
                 source.cancel()
             else:
                 source_loop.call_soon_threadsafe(source.cancel)
@@ -392,7 +392,7 @@ def _chain_future(source, destination):
         if (destination.cancelled() and
                 dest_loop is not None and dest_loop.is_closed()):
             return
-        if dest_loop is None or dest_loop is source_loop:
+        if dest_loop is None or dest_loop is events._get_running_loop():
             _set_state(destination, source)
         else:
             if dest_loop.is_closed():
index 54692713011278286cebec7fa6666a3f375a3173..361ad8a7874077fbb1240ff8f0b3bc8b2e548ca6 100644 (file)
@@ -3552,6 +3552,30 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase):
         (loop, context), kwargs = callback.call_args
         self.assertEqual(context['exception'], exc_context.exception)
 
+    def test_run_coroutine_threadsafe_and_cancel(self):
+        task = None
+        thread_future = None
+        # Use a custom task factory to capture the created Task
+        def task_factory(loop, coro):
+            nonlocal task
+            task = asyncio.Task(coro, loop=loop)
+            return task
+
+        self.addCleanup(self.loop.set_task_factory,
+                        self.loop.get_task_factory())
+
+        async def target():
+            nonlocal thread_future
+            self.loop.set_task_factory(task_factory)
+            thread_future = asyncio.run_coroutine_threadsafe(asyncio.sleep(10), self.loop)
+            await asyncio.sleep(0)
+
+            thread_future.cancel()
+
+        self.loop.run_until_complete(target())
+        self.assertTrue(task.cancelled())
+        self.assertTrue(thread_future.cancelled())
+
 
 class SleepTests(test_utils.TestCase):
     def setUp(self):
index 6cc30812cced119143105d83bafede7fe42df6db..55713d3b2868357c8c7a264fe290c6de33113f22 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -2074,6 +2074,7 @@ Xiang Zhang
 Robert Xiao
 Florent Xicluna
 Yanbo, Xie
+Kaisheng Xu
 Xinhang Xu
 Arnon Yaari
 Alakshendra Yadav
diff --git a/Misc/NEWS.d/next/Library/2025-11-18-15-48-13.gh-issue-105836.sbUw24.rst b/Misc/NEWS.d/next/Library/2025-11-18-15-48-13.gh-issue-105836.sbUw24.rst
new file mode 100644 (file)
index 0000000..d2edc5b
--- /dev/null
@@ -0,0 +1,2 @@
+Fix :meth:`asyncio.run_coroutine_threadsafe` leaving underlying cancelled
+asyncio task running.