From 1f178daae7277625448970b9a07bfb1b9bf5419e Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 10 Oct 2025 21:58:23 +0530 Subject: [PATCH 1/2] [3.14] gh-139894: fix incorrect sharing of current task while forking in `asyncio` (GH-139897) 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`. (cherry picked from commit b881df47ff1adca515d1de04f689160ddae72142) Co-authored-by: Kumar Aditya --- Lib/test/test_asyncio/test_unix_events.py | 43 +++++++++++++++++-- ...-10-10-11-22-50.gh-issue-139894.ECAXqj.rst | 1 + Modules/posixmodule.c | 10 +++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index 22982dc9d8aefe..0365954b266ead 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -15,7 +15,7 @@ from unittest import mock from test import support -from test.support import os_helper +from test.support import os_helper, warnings_helper from test.support import socket_helper from test.support import wait_process from test.support import hashlib_helper @@ -1180,11 +1180,46 @@ async def runner(): @support.requires_fork() -class TestFork(unittest.IsolatedAsyncioTestCase): +class TestFork(unittest.TestCase): + + 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) - async def test_fork_not_share_event_loop(self): + def test_fork_not_share_event_loop(self): # The forked process should not share the event loop with the parent - loop = asyncio.get_running_loop() + 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) 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 index 00000000000000..05a977ad119e07 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst @@ -0,0 +1 @@ +Fix incorrect sharing of current task with the child process while forking in :mod:`asyncio`. Patch by Kumar Aditya. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3dc62240952f68..2dad3352982f7b 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -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 From 55c6dab8c7b15adecf05a842b18d36cb4b84401c Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 10 Oct 2025 22:03:41 +0530 Subject: [PATCH 2/2] Update Lib/test/test_asyncio/test_unix_events.py --- Lib/test/test_asyncio/test_unix_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index 0365954b266ead..520f5c733c3bf2 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -15,7 +15,7 @@ from unittest import mock from test import support -from test.support import os_helper, warnings_helper +from test.support import os_helper from test.support import socket_helper from test.support import wait_process from test.support import hashlib_helper