]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-83856: Honor atexit for all multiprocessing start methods (GH-114279)
authorTian Gao <gaogaotiantian@hotmail.com>
Fri, 3 May 2024 18:45:46 +0000 (11:45 -0700)
committerGitHub <noreply@github.com>
Fri, 3 May 2024 18:45:46 +0000 (11:45 -0700)
Use atexit for all multiprocessing start methods to cleanup.
See the GH-114279 PR discussion and related issue for details as to why.

Lib/multiprocessing/forkserver.py
Lib/multiprocessing/popen_fork.py
Lib/multiprocessing/process.py
Lib/test/_test_multiprocessing.py
Misc/NEWS.d/next/Library/2024-01-19-05-40-46.gh-issue-83856.jN5M80.rst [new file with mode: 0644]

index 4642707dae2f4e252554a2f71575289e9064b499..53b8c492675878a8765af1f1ab0f606c11319373 100644 (file)
@@ -1,3 +1,4 @@
+import atexit
 import errno
 import os
 import selectors
@@ -271,6 +272,8 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
                                 selector.close()
                                 unused_fds = [alive_r, child_w, sig_r, sig_w]
                                 unused_fds.extend(pid_to_fd.values())
+                                atexit._clear()
+                                atexit.register(util._exit_function)
                                 code = _serve_one(child_r, fds,
                                                   unused_fds,
                                                   old_handlers)
@@ -278,6 +281,7 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
                                 sys.excepthook(*sys.exc_info())
                                 sys.stderr.flush()
                             finally:
+                                atexit._run_exitfuncs()
                                 os._exit(code)
                         else:
                             # Send pid to client process
index 625981cf47627cab9d03cae4d3c6d8224a9375cc..a57ef6bdad5ccc667866ede2fb695d8eb55e6ac5 100644 (file)
@@ -1,3 +1,4 @@
+import atexit
 import os
 import signal
 
@@ -66,10 +67,13 @@ class Popen(object):
         self.pid = os.fork()
         if self.pid == 0:
             try:
+                atexit._clear()
+                atexit.register(util._exit_function)
                 os.close(parent_r)
                 os.close(parent_w)
                 code = process_obj._bootstrap(parent_sentinel=child_r)
             finally:
+                atexit._run_exitfuncs()
                 os._exit(code)
         else:
             os.close(child_w)
index 271ba3fd325138d75a7707d97d89b7003c5fcbc4..b45f7df476f7d8cb853c21c308656b3914e55c7d 100644 (file)
@@ -310,11 +310,8 @@ class BaseProcess(object):
                 # _run_after_forkers() is executed
                 del old_process
             util.info('child process calling self.run()')
-            try:
-                self.run()
-                exitcode = 0
-            finally:
-                util._exit_function()
+            self.run()
+            exitcode = 0
         except SystemExit as e:
             if e.code is None:
                 exitcode = 0
index 5fc4181a1eeadb6b3f80dd7665e5e6868d60fe19..46afdfca331a23aada434dc1da77df72b99a304d 100644 (file)
@@ -6161,6 +6161,29 @@ class TestNamedResource(unittest.TestCase):
         self.assertFalse(err, msg=err.decode('utf-8'))
 
 
+class _TestAtExit(BaseTestCase):
+
+    ALLOWED_TYPES = ('processes',)
+
+    @classmethod
+    def _write_file_at_exit(self, output_path):
+        import atexit
+        def exit_handler():
+            with open(output_path, 'w') as f:
+                f.write("deadbeef")
+        atexit.register(exit_handler)
+
+    def test_atexit(self):
+        # gh-83856
+        with os_helper.temp_dir() as temp_dir:
+            output_path = os.path.join(temp_dir, 'output.txt')
+            p = self.Process(target=self._write_file_at_exit, args=(output_path,))
+            p.start()
+            p.join()
+            with open(output_path) as f:
+                self.assertEqual(f.read(), 'deadbeef')
+
+
 class MiscTestCase(unittest.TestCase):
     def test__all__(self):
         # Just make sure names in not_exported are excluded
diff --git a/Misc/NEWS.d/next/Library/2024-01-19-05-40-46.gh-issue-83856.jN5M80.rst b/Misc/NEWS.d/next/Library/2024-01-19-05-40-46.gh-issue-83856.jN5M80.rst
new file mode 100644 (file)
index 0000000..b2889f2
--- /dev/null
@@ -0,0 +1 @@
+Honor :mod:`atexit` for all :mod:`multiprocessing` start methods