]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139894: fix incorrect sharing of current task while forking in `asyncio` (#139897)
authorKumar Aditya <kumaraditya@python.org>
Fri, 10 Oct 2025 16:28:23 +0000 (21:58 +0530)
committerGitHub <noreply@github.com>
Fri, 10 Oct 2025 16:28:23 +0000 (21:58 +0530)
Fix incorrect sharing of current task with the forked child process by clearing thread state's current task and current loop in `PyOS_AfterFork_Child`.

Lib/test/test_asyncio/test_unix_events.py
Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst [new file with mode: 0644]
Modules/posixmodule.c

index a69a5e32b1b2bd7cda655a5c08cadab24d356ddc..d2b3de3b9a4cb614954a0c7d7301561c3e21cb74 100644 (file)
@@ -1180,32 +1180,68 @@ class TestFunctional(unittest.TestCase):
 
 
 @support.requires_fork()
-class TestFork(unittest.IsolatedAsyncioTestCase):
+class TestFork(unittest.TestCase):
 
-    async def test_fork_not_share_event_loop(self):
-        with warnings_helper.ignore_fork_in_thread_deprecation_warnings():
-            # The forked process should not share the event loop with the parent
-            loop = asyncio.get_running_loop()
-            r, w = os.pipe()
-            self.addCleanup(os.close, r)
-            self.addCleanup(os.close, w)
-            pid = os.fork()
-            if pid == 0:
-                # child
-                try:
-                    loop = asyncio.get_event_loop()
-                    os.write(w, b'LOOP:' + str(id(loop)).encode())
-                except RuntimeError:
-                    os.write(w, b'NO LOOP')
-                except BaseException as e:
-                    os.write(w, b'ERROR:' + ascii(e).encode())
-                finally:
-                    os._exit(0)
-            else:
-                # parent
-                result = os.read(r, 100)
-                self.assertEqual(result, b'NO LOOP')
-                wait_process(pid, exitcode=0)
+    @warnings_helper.ignore_fork_in_thread_deprecation_warnings()
+    def test_fork_not_share_current_task(self):
+        loop = object()
+        task = object()
+        asyncio._set_running_loop(loop)
+        self.addCleanup(asyncio._set_running_loop, None)
+        asyncio.tasks._enter_task(loop, task)
+        self.addCleanup(asyncio.tasks._leave_task, loop, task)
+        self.assertIs(asyncio.current_task(), task)
+        r, w = os.pipe()
+        self.addCleanup(os.close, r)
+        self.addCleanup(os.close, w)
+        pid = os.fork()
+        if pid == 0:
+            # child
+            try:
+                asyncio._set_running_loop(loop)
+                current_task = asyncio.current_task()
+                if current_task is None:
+                    os.write(w, b'NO TASK')
+                else:
+                    os.write(w, b'TASK:' + str(id(current_task)).encode())
+            except BaseException as e:
+                os.write(w, b'ERROR:' + ascii(e).encode())
+            finally:
+                asyncio._set_running_loop(None)
+                os._exit(0)
+        else:
+            # parent
+            result = os.read(r, 100)
+            self.assertEqual(result, b'NO TASK')
+            wait_process(pid, exitcode=0)
+
+    @warnings_helper.ignore_fork_in_thread_deprecation_warnings()
+    def test_fork_not_share_event_loop(self):
+        # The forked process should not share the event loop with the parent
+        loop = object()
+        asyncio._set_running_loop(loop)
+        self.assertIs(asyncio.get_running_loop(), loop)
+        self.addCleanup(asyncio._set_running_loop, None)
+        r, w = os.pipe()
+        self.addCleanup(os.close, r)
+        self.addCleanup(os.close, w)
+        pid = os.fork()
+        if pid == 0:
+            # child
+            try:
+                loop = asyncio.get_event_loop()
+                os.write(w, b'LOOP:' + str(id(loop)).encode())
+            except RuntimeError:
+                os.write(w, b'NO LOOP')
+            except BaseException as e:
+                os.write(w, b'ERROR:' + ascii(e).encode())
+            finally:
+                os._exit(0)
+        else:
+            # parent
+            result = os.read(r, 100)
+            self.assertEqual(result, b'NO LOOP')
+            wait_process(pid, exitcode=0)
 
     @warnings_helper.ignore_fork_in_thread_deprecation_warnings()
     @hashlib_helper.requires_hashdigest('md5')
diff --git a/Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst b/Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst
new file mode 100644 (file)
index 0000000..05a977a
--- /dev/null
@@ -0,0 +1 @@
+Fix incorrect sharing of current task with the child process while forking in :mod:`asyncio`. Patch by Kumar Aditya.
index 7a2e36bf29420561f6a0100b231c9b8832f19f29..8278902cbeb349bff292f2adf02af1be9e49c1f3 100644 (file)
@@ -689,6 +689,14 @@ reset_remotedebug_data(PyThreadState *tstate)
            _Py_MAX_SCRIPT_PATH_SIZE);
 }
 
+static void
+reset_asyncio_state(_PyThreadStateImpl *tstate)
+{
+    llist_init(&tstate->asyncio_tasks_head);
+    tstate->asyncio_running_loop = NULL;
+    tstate->asyncio_running_task = NULL;
+}
+
 
 void
 PyOS_AfterFork_Child(void)
@@ -725,6 +733,8 @@ PyOS_AfterFork_Child(void)
 
     reset_remotedebug_data(tstate);
 
+    reset_asyncio_state((_PyThreadStateImpl *)tstate);
+
     // Remove the dead thread states. We "start the world" once we are the only
     // thread state left to undo the stop the world call in `PyOS_BeforeFork`.
     // That needs to happen before `_PyThreadState_DeleteList`, because that