Skip to content

Commit 8b44a77

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

File tree

3 files changed

+65
-8
lines changed

3 files changed

+65
-8
lines changed

Lib/multiprocessing/forkserver.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,24 @@ def ensure_running(self):
135135
if not pid:
136136
# still alive
137137
return
138-
# dead, launch it again
138+
if self._forkserver_address is not None:
139+
# forkserver may be inherited from parent, is it still running?
140+
try:
141+
# check by writing a probe
142+
os.write(self._forkserver_alive_fd, b'P')
143+
except OSError:
144+
pass
145+
else:
146+
# still alive
147+
return
148+
149+
# dead, launch it again
150+
if self._forkserver_pid is not None:
139151
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
152+
self._forkserver_authkey = None
153+
self._forkserver_address = None
154+
self._forkserver_alive_fd = None
155+
self._forkserver_pid = None
144156

145157
cmd = ('from multiprocessing.forkserver import main; ' +
146158
'main(%d, %d, %r, **%r)')
@@ -207,6 +219,7 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
207219
os.close(authkey_r)
208220
else:
209221
authkey = b''
222+
_forkserver._forkserver_authkey = authkey
210223

211224
if preload:
212225
if sys_path is not None:
@@ -268,9 +281,16 @@ def sigchld_handler(*_unused):
268281
break
269282

270283
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
284+
data = os.read(alive_r, 1)
285+
if data == b'P':
286+
# probe from client
287+
pass
288+
elif data == b'':
289+
# EOF because no more client processes left
290+
raise SystemExit
291+
else:
292+
raise RuntimeError("Unknown data received in alive fd")
293+
274294

275295
if sig_r in rfds:
276296
# 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)