https://github.com/python/cpython/commit/b881df47ff1adca515d1de04f689160ddae72142
commit: b881df47ff1adca515d1de04f689160ddae72142
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2025-10-10T21:58:23+05:30
summary:
gh-139894: fix incorrect sharing of current task while forking in `asyncio`
(#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`.
files:
A Misc/NEWS.d/next/Library/2025-10-10-11-22-50.gh-issue-139894.ECAXqj.rst
M Lib/test/test_asyncio/test_unix_events.py
M Modules/posixmodule.c
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/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 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
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]