From c376dea326ca112d1e6e3bae95ce1cc58f37d127 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Sun, 7 Sep 2025 19:49:54 +1200 Subject: [PATCH] [3.13] gh-126631: gh-137996: fix pre-loading of `__main__` (GH-135295) gh-126631: gh-137996: fix pre-loading of `__main__` The `main_path` parameter was renamed `init_main_from_name`, update the forkserver code accordingly. This was leading to slower startup times when people were trying to preload the main module. --------- (cherry picked from commit 0912b3a6dbd226bea79eb8d70d5a06230804d4cb) Co-authored-by: Duane Griffin Co-authored-by: Gregory P. Smith --- Lib/multiprocessing/forkserver.py | 11 ++++++----- Lib/test/_test_multiprocessing.py | 12 ++++++++++++ Lib/test/mp_preload_main.py | 14 ++++++++++++++ .../2025-06-10-21-00-48.gh-issue-126631.eITVJd.rst | 2 ++ 4 files changed, 34 insertions(+), 5 deletions(-) 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/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index 0c84c0baa2a099..e243442e7a15bd 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -127,12 +127,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') - data = {x: y for x, y in data.items() if x in desired_keys} - else: - data = {} + 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') @@ -147,7 +148,7 @@ def ensure_running(self): try: fds_to_pass = [listener.fileno(), alive_r] cmd %= (listener.fileno(), alive_r, self._preload_modules, - data) + main_kws) exe = spawn.get_executable() args = [exe] + util._args_from_interpreter_flags() args += ['-c', cmd] diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 22e19871e50d25..c22ce769c48963 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -6545,6 +6545,18 @@ 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') + _, out, err = test.support.script_helper.assert_python_ok(name) + self.assertEqual(err, b'') + + # The trailing empty string comes from split() on output ending with \n + 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..acb342822ecc89 --- /dev/null +++ b/Lib/test/mp_preload_main.py @@ -0,0 +1,14 @@ +import multiprocessing + +print(f"{__name__}") + +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.