mirror of
https://github.com/python/cpython.git
synced 2025-11-24 20:30:18 +00:00
gh-139894: fix incorrect sharing of current task while forking in asyncio (#139897)
Some checks are pending
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
Some checks are pending
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
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`.
This commit is contained in:
parent
c7f1da97eb
commit
b881df47ff
3 changed files with 72 additions and 25 deletions
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Fix incorrect sharing of current task with the child process while forking in :mod:`asyncio`. Patch by Kumar Aditya.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue