From ec334634474601c073bf1d4badce96f76cc18224 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Mon, 9 Jun 2025 23:37:16 +1200 Subject: [PATCH 1/2] gh-126631: fix pre-loading of __main__ The `main_path` parameter was renamed `init_main_from_name`, update the forkserver code accordingly. --- Lib/multiprocessing/forkserver.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index 681af2610e9b37..221578f0c9fd1e 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -145,12 +145,13 @@ def ensure_running(self): cmd = ('from multiprocessing.forkserver import main; ' + 'main(%d, %d, %r, **%r)') + main_kws = {} if self._preload_modules: - desired_keys = {'main_path', 'sys_path'} data = spawn.get_preparation_data('ignore') - main_kws = {x: y for x, y in data.items() if x in desired_keys} - else: - main_kws = {} + if 'sys_path' in data: + main_kws['sys_path'] = data['sys_path'] + if 'init_main_from_path' in data: + main_kws['main_path'] = data['init_main_from_path'] with socket.socket(socket.AF_UNIX) as listener: address = connection.arbitrary_address('AF_UNIX') From 0234918529c4828124c0c8e2e1b3ab77b1f1679e Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Tue, 10 Jun 2025 14:22:34 +1200 Subject: [PATCH 2/2] Add regression test & blurb --- Lib/test/_test_multiprocessing.py | 13 ++++++++++++ Lib/test/mp_preload_main.py | 20 +++++++++++++++++++ ...-06-10-21-00-48.gh-issue-126631.eITVJd.rst | 2 ++ 3 files changed, 35 insertions(+) create mode 100644 Lib/test/mp_preload_main.py create mode 100644 Misc/NEWS.d/next/Library/2025-06-10-21-00-48.gh-issue-126631.eITVJd.rst diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 75f31d858d3306..8ca165ad7c4432 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -6866,6 +6866,19 @@ def child(): self.assertEqual(q.get_nowait(), "done") close_queue(q) + def test_preload_main(self): + # gh-126631: Check that __main__ can be pre-loaded + if multiprocessing.get_start_method() != "forkserver": + self.skipTest("forkserver specific test") + + name = os.path.join(os.path.dirname(__file__), 'mp_preload_main.py') + rc, out, err = test.support.script_helper.assert_python_ok(name) + self.assertFalse(err, msg=err.decode()) + self.assertEqual(rc, 0) + + # TODO: Where is the extra empty line coming from? + out = out.decode().split("\n") + self.assertEqual(out, ['__main__', '__mp_main__', 'f', 'f', '']) # # Mixins diff --git a/Lib/test/mp_preload_main.py b/Lib/test/mp_preload_main.py new file mode 100644 index 00000000000000..f0bdcb169b0c7e --- /dev/null +++ b/Lib/test/mp_preload_main.py @@ -0,0 +1,20 @@ +import multiprocessing +import sys + +print(f"{__name__}") + +# TODO: This is necessary as the fork server doesn't flush output after +# preloading modules, and hence buffered output is inherited by child +# processes. See gh-135335 (this should be removed once that is fixed). +sys.stdout.flush() + +def f(): + print("f") + +if __name__ == "__main__": + ctx = multiprocessing.get_context("forkserver") + ctx.set_forkserver_preload(['__main__']) + for _ in range(2): + p = ctx.Process(target=f) + p.start() + p.join() diff --git a/Misc/NEWS.d/next/Library/2025-06-10-21-00-48.gh-issue-126631.eITVJd.rst b/Misc/NEWS.d/next/Library/2025-06-10-21-00-48.gh-issue-126631.eITVJd.rst new file mode 100644 index 00000000000000..195253b1ec1e39 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-10-21-00-48.gh-issue-126631.eITVJd.rst @@ -0,0 +1,2 @@ +Fix :mod:`multiprocessing` ``forkserver`` bug which prevented ``__main__`` +from being preloaded.