I patched forkserver.py for debugging and found that the test case does not cause SystemExit to be raised here until the main function of the test case returns:
Meanwhile, it seems that the corresponding write_signed call was successful on the forkserver side.
I did some more debugging and found that the underlying issue is a thread safety issue for the self.returncode setting in Popen.poll(), and the load test runs better with this patch:
--- a/Lib/multiprocessing/popen_forkserver.py+++ b/Lib/multiprocessing/popen_forkserver.py @@ -1,5 +1,6 @@
import io
import os
+import threading
from .context import reduction, set_spawning_popen
if not reduction.HAVE_SEND_HANDLE:
@@ -32,6 +33,7 @@
def __init__(self, process_obj):
self._fds = []
+ self._poll_lock = threading.Lock()
super().__init__(process_obj)
def duplicate_for_child(self, fd):
@@ -65,7 +67,9 @@
if not wait([self.sentinel], timeout):
return None
try:
- self.returncode = forkserver.read_signed(self.sentinel)+ with self._poll_lock:+ if self.returncode is None:+ self.returncode = forkserver.read_signed(self.sentinel)
except (OSError, EOFError):
# This should not happen usually, but perhaps the forkserver
# process itself got killed
I created the test case because the same issue was triggered by a program called egencache here:
I found that the root cause was thread safety for self.returncode = forkserver.read_signed(self.sentinel) setting in Popen.poll(), and edited my first comment to include a patch that makes the load test run better.
Activity
picnixz commented on Nov 1, 2025
cc @gpshead
zmedico commented on Nov 1, 2025
I found that the root cause was thread safety for self.returncode = forkserver.read_signed(self.sentinel) setting in Popen.poll(), and edited my first comment to include a patch that makes the load test run better.
duaneg commented on Nov 2, 2025
See also #130895, which I think has the same root cause, at least with regards forkserver.