From 8c231ce43e668247c8324b2507d519232043a988 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 10 Oct 2025 16:43:48 +0530 Subject: [PATCH 1/2] fix forking with current task set --- Lib/test/test_asyncio/test_unix_events.py | 86 ++++++++++++++++------- Modules/posixmodule.c | 10 +++ 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index a69a5e32b1b2bd..d2b3de3b9a4cb6 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -1180,32 +1180,68 @@ async def runner(): @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/Modules/posixmodule.c b/Modules/posixmodule.c index 7a2e36bf294205..8278902cbeb349 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 42f5468d883cdd7cf969b76e1ac25c21aca500ad Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:22:56 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst 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.