Skip to content

Commit 998c385

Browse files
gh-83856: Honor atexit for all multiprocessing start methods (GH-114279)
Use atexit for all multiprocessing start methods to cleanup. See the GH-114279 PR discussion and related issue for details as to why.
1 parent cb57a52 commit 998c385

File tree

5 files changed

+34
-5
lines changed

5 files changed

+34
-5
lines changed

Lib/multiprocessing/forkserver.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import atexit
12
import errno
23
import os
34
import selectors
@@ -271,13 +272,16 @@ def sigchld_handler(*_unused):
271272
selector.close()
272273
unused_fds = [alive_r, child_w, sig_r, sig_w]
273274
unused_fds.extend(pid_to_fd.values())
275+
atexit._clear()
276+
atexit.register(util._exit_function)
274277
code = _serve_one(child_r, fds,
275278
unused_fds,
276279
old_handlers)
277280
except Exception:
278281
sys.excepthook(*sys.exc_info())
279282
sys.stderr.flush()
280283
finally:
284+
atexit._run_exitfuncs()
281285
os._exit(code)
282286
else:
283287
# Send pid to client process

Lib/multiprocessing/popen_fork.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import atexit
12
import os
23
import signal
34

@@ -66,10 +67,13 @@ def _launch(self, process_obj):
6667
self.pid = os.fork()
6768
if self.pid == 0:
6869
try:
70+
atexit._clear()
71+
atexit.register(util._exit_function)
6972
os.close(parent_r)
7073
os.close(parent_w)
7174
code = process_obj._bootstrap(parent_sentinel=child_r)
7275
finally:
76+
atexit._run_exitfuncs()
7377
os._exit(code)
7478
else:
7579
os.close(child_w)

Lib/multiprocessing/process.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,8 @@ def _bootstrap(self, parent_sentinel=None):
310310
# _run_after_forkers() is executed
311311
del old_process
312312
util.info('child process calling self.run()')
313-
try:
314-
self.run()
315-
exitcode = 0
316-
finally:
317-
util._exit_function()
313+
self.run()
314+
exitcode = 0
318315
except SystemExit as e:
319316
if e.code is None:
320317
exitcode = 0

Lib/test/_test_multiprocessing.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6161,6 +6161,29 @@ def submain(): pass
61616161
self.assertFalse(err, msg=err.decode('utf-8'))
61626162

61636163

6164+
class _TestAtExit(BaseTestCase):
6165+
6166+
ALLOWED_TYPES = ('processes',)
6167+
6168+
@classmethod
6169+
def _write_file_at_exit(self, output_path):
6170+
import atexit
6171+
def exit_handler():
6172+
with open(output_path, 'w') as f:
6173+
f.write("deadbeef")
6174+
atexit.register(exit_handler)
6175+
6176+
def test_atexit(self):
6177+
# gh-83856
6178+
with os_helper.temp_dir() as temp_dir:
6179+
output_path = os.path.join(temp_dir, 'output.txt')
6180+
p = self.Process(target=self._write_file_at_exit, args=(output_path,))
6181+
p.start()
6182+
p.join()
6183+
with open(output_path) as f:
6184+
self.assertEqual(f.read(), 'deadbeef')
6185+
6186+
61646187
class MiscTestCase(unittest.TestCase):
61656188
def test__all__(self):
61666189
# Just make sure names in not_exported are excluded
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Honor :mod:`atexit` for all :mod:`multiprocessing` start methods

0 commit comments

Comments
 (0)