Skip to content

Commit ed7c08e

Browse files
committed
gh-86354: Fix child processes not reusing forkserver created by parent
1 parent 4e7e2dd commit ed7c08e

File tree

3 files changed

+60
-12
lines changed

3 files changed

+60
-12
lines changed

Lib/multiprocessing/forkserver.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,21 @@ def ensure_running(self):
129129
'''
130130
with self._lock:
131131
resource_tracker.ensure_running()
132-
if self._forkserver_pid is not None:
132+
if self._forkserver_address is not None:
133133
# forkserver was launched before, is it still running?
134-
pid, status = os.waitpid(self._forkserver_pid, os.WNOHANG)
135-
if not pid:
134+
try:
135+
# check by writing a probe
136+
os.write(self._forkserver_alive_fd, b'P')
137+
except OSError:
138+
# dead, launch it again
139+
os.close(self._forkserver_alive_fd)
140+
self._forkserver_authkey = None
141+
self._forkserver_address = None
142+
self._forkserver_alive_fd = None
143+
self._forkserver_pid = None
144+
else:
136145
# still alive
137146
return
138-
# dead, launch it again
139-
os.close(self._forkserver_alive_fd)
140-
self._forkserver_authkey = None
141-
self._forkserver_address = None
142-
self._forkserver_alive_fd = None
143-
self._forkserver_pid = None
144147

145148
cmd = ('from multiprocessing.forkserver import main; ' +
146149
'main(%d, %d, %r, **%r)')
@@ -207,6 +210,7 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
207210
os.close(authkey_r)
208211
else:
209212
authkey = b''
213+
_forkserver._forkserver_authkey = authkey
210214

211215
if preload:
212216
if sys_path is not None:
@@ -268,9 +272,16 @@ def sigchld_handler(*_unused):
268272
break
269273

270274
if alive_r in rfds:
271-
# EOF because no more client processes left
272-
assert os.read(alive_r, 1) == b'', "Not at EOF?"
273-
raise SystemExit
275+
data = os.read(alive_r, 1)
276+
if data == b'P':
277+
# probe from client
278+
pass
279+
elif data == b'':
280+
# EOF because no more client processes left
281+
raise SystemExit
282+
else:
283+
raise RuntimeError("Unknown data received in alive fd")
284+
274285

275286
if sig_r in rfds:
276287
# Got SIGCHLD

Lib/test/_test_multiprocessing.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,42 @@ def test_forkserver_without_auth_fails(self):
10021002
proc.start()
10031003
proc.join()
10041004

1005+
@classmethod
1006+
def _create_child_and_get_ppid(cls, queue, remaining):
1007+
queue.put(os.getppid())
1008+
remaining -= 1
1009+
if remaining > 0:
1010+
proc = cls.Process(target=cls._create_child_and_get_ppid, args=(queue, remaining))
1011+
proc.start()
1012+
proc.join()
1013+
1014+
1015+
def test_forkserver_reuse(self):
1016+
# existing forkserver spawned by parent should be reused by children
1017+
if self.TYPE == "threads":
1018+
self.skipTest(f"test not appropriate for {self.TYPE}")
1019+
if multiprocessing.get_start_method() != "forkserver":
1020+
self.skipTest("forkserver start method specific")
1021+
1022+
forkserver = multiprocessing.forkserver._forkserver
1023+
forkserver.ensure_running()
1024+
self.assertTrue(forkserver._forkserver_pid)
1025+
forkserver_pid = forkserver._forkserver_pid
1026+
1027+
# start children recursively
1028+
ppid_queue = self.Queue()
1029+
proc = self.Process(target=self._create_child_and_get_ppid, args=(ppid_queue, 3))
1030+
proc.start()
1031+
proc.join()
1032+
for i in range(3):
1033+
# all descendants should have the same ppid (forkserver)
1034+
ppid = ppid_queue.get()
1035+
assert ppid == forkserver_pid
1036+
1037+
# forkserver should live after descendants ends
1038+
forkserver.ensure_running()
1039+
assert forkserver._forkserver_pid == forkserver_pid
1040+
10051041
#
10061042
#
10071043
#
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix child processes not reusing forkserver created by parent

0 commit comments

Comments
 (0)