Skip to content

Commit 72f25a8

Browse files
[3.14] gh-139894: fix incorrect sharing of current task while forking in asyncio (GH-139897) (#139913)
* [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 b881df4) Co-authored-by: Kumar Aditya <[email protected]> * Update Lib/test/test_asyncio/test_unix_events.py
1 parent 60d15e1 commit 72f25a8

File tree

3 files changed

+49
-3
lines changed

3 files changed

+49
-3
lines changed

Lib/test/test_asyncio/test_unix_events.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,11 +1180,46 @@ async def runner():
11801180

11811181

11821182
@support.requires_fork()
1183-
class TestFork(unittest.IsolatedAsyncioTestCase):
1183+
class TestFork(unittest.TestCase):
1184+
1185+
def test_fork_not_share_current_task(self):
1186+
loop = object()
1187+
task = object()
1188+
asyncio._set_running_loop(loop)
1189+
self.addCleanup(asyncio._set_running_loop, None)
1190+
asyncio.tasks._enter_task(loop, task)
1191+
self.addCleanup(asyncio.tasks._leave_task, loop, task)
1192+
self.assertIs(asyncio.current_task(), task)
1193+
r, w = os.pipe()
1194+
self.addCleanup(os.close, r)
1195+
self.addCleanup(os.close, w)
1196+
pid = os.fork()
1197+
if pid == 0:
1198+
# child
1199+
try:
1200+
asyncio._set_running_loop(loop)
1201+
current_task = asyncio.current_task()
1202+
if current_task is None:
1203+
os.write(w, b'NO TASK')
1204+
else:
1205+
os.write(w, b'TASK:' + str(id(current_task)).encode())
1206+
except BaseException as e:
1207+
os.write(w, b'ERROR:' + ascii(e).encode())
1208+
finally:
1209+
asyncio._set_running_loop(None)
1210+
os._exit(0)
1211+
else:
1212+
# parent
1213+
result = os.read(r, 100)
1214+
self.assertEqual(result, b'NO TASK')
1215+
wait_process(pid, exitcode=0)
11841216

1185-
async def test_fork_not_share_event_loop(self):
1217+
def test_fork_not_share_event_loop(self):
11861218
# The forked process should not share the event loop with the parent
1187-
loop = asyncio.get_running_loop()
1219+
loop = object()
1220+
asyncio._set_running_loop(loop)
1221+
self.assertIs(asyncio.get_running_loop(), loop)
1222+
self.addCleanup(asyncio._set_running_loop, None)
11881223
r, w = os.pipe()
11891224
self.addCleanup(os.close, r)
11901225
self.addCleanup(os.close, w)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix incorrect sharing of current task with the child process while forking in :mod:`asyncio`. Patch by Kumar Aditya.

Modules/posixmodule.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,14 @@ reset_remotedebug_data(PyThreadState *tstate)
689689
_Py_MAX_SCRIPT_PATH_SIZE);
690690
}
691691

692+
static void
693+
reset_asyncio_state(_PyThreadStateImpl *tstate)
694+
{
695+
llist_init(&tstate->asyncio_tasks_head);
696+
tstate->asyncio_running_loop = NULL;
697+
tstate->asyncio_running_task = NULL;
698+
}
699+
692700

693701
void
694702
PyOS_AfterFork_Child(void)
@@ -725,6 +733,8 @@ PyOS_AfterFork_Child(void)
725733

726734
reset_remotedebug_data(tstate);
727735

736+
reset_asyncio_state((_PyThreadStateImpl *)tstate);
737+
728738
// Remove the dead thread states. We "start the world" once we are the only
729739
// thread state left to undo the stop the world call in `PyOS_BeforeFork`.
730740
// That needs to happen before `_PyThreadState_DeleteList`, because that

0 commit comments

Comments
 (0)