https://github.com/python/cpython/commit/a0a1aa3125a1547830b38d93b862204b581361d2
commit: a0a1aa3125a1547830b38d93b862204b581361d2
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: vstinner <[email protected]>
date: 2025-06-18T12:40:21Z
summary:

[3.13] gh-135335: flush stdout/stderr in forkserver after preloading modules 
(GH-135338) (#135671)

gh-135335: flush stdout/stderr in forkserver after preloading modules 
(GH-135338)

If a preloaded module writes to stdout or stderr, and the stream is buffered,
child processes will inherit the buffered data after forking. Attempt to
prevent this by flushing the streams after preload.
(cherry picked from commit 9877d191f441741fc27ae5e7a6dd7ab6d4bcc6b7)

Co-authored-by: Duane Griffin <[email protected]>
Co-authored-by: Mikhail Efimov <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>

files:
A Lib/test/mp_preload_flush.py
A Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst
M Lib/multiprocessing/forkserver.py
M Lib/test/_test_multiprocessing.py

diff --git a/Lib/multiprocessing/forkserver.py 
b/Lib/multiprocessing/forkserver.py
index bff7fb91d974b3..0c84c0baa2a099 100644
--- a/Lib/multiprocessing/forkserver.py
+++ b/Lib/multiprocessing/forkserver.py
@@ -182,6 +182,10 @@ def main(listener_fd, alive_r, preload, main_path=None, 
sys_path=None):
             except ImportError:
                 pass
 
+        # gh-135335: flush stdout/stderr in case any of the preloaded modules
+        # wrote to them, otherwise children might inherit buffered data
+        util._flush_std_streams()
+
     util._close_stdin()
 
     sig_r, sig_w = os.pipe()
diff --git a/Lib/test/_test_multiprocessing.py 
b/Lib/test/_test_multiprocessing.py
index 0b8de96f1b0c96..22e19871e50d25 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -6451,6 +6451,35 @@ def test_child_sys_path(self):
         self.assertEqual(child_sys_path[1:], sys.path[1:])
         self.assertIsNone(import_error, msg=f"child could not import 
{self._mod_name}")
 
+    def test_std_streams_flushed_after_preload(self):
+        # gh-135335: Check fork server flushes standard streams after
+        # preloading modules
+        if multiprocessing.get_start_method() != "forkserver":
+            self.skipTest("forkserver specific test")
+
+        # Create a test module in the temporary directory on the child's path
+        # TODO: This can all be simplified once gh-126631 is fixed and we can
+        #       use __main__ instead of a module.
+        dirname = os.path.join(self._temp_dir, 'preloaded_module')
+        init_name = os.path.join(dirname, '__init__.py')
+        os.mkdir(dirname)
+        with open(init_name, "w") as f:
+            cmd = '''if 1:
+                import sys
+                print('stderr', end='', file=sys.stderr)
+                print('stdout', end='', file=sys.stdout)
+            '''
+            f.write(cmd)
+
+        name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py')
+        env = {'PYTHONPATH': self._temp_dir}
+        _, out, err = test.support.script_helper.assert_python_ok(name, **env)
+
+        # Check stderr first, as it is more likely to be useful to see in the
+        # event of a failure.
+        self.assertEqual(err.decode().rstrip(), 'stderr')
+        self.assertEqual(out.decode().rstrip(), 'stdout')
+
 
 class MiscTestCase(unittest.TestCase):
     def test__all__(self):
diff --git a/Lib/test/mp_preload_flush.py b/Lib/test/mp_preload_flush.py
new file mode 100644
index 00000000000000..3501554d366a21
--- /dev/null
+++ b/Lib/test/mp_preload_flush.py
@@ -0,0 +1,15 @@
+import multiprocessing
+import sys
+
+modname = 'preloaded_module'
+if __name__ == '__main__':
+    if modname in sys.modules:
+        raise AssertionError(f'{modname!r} is not in sys.modules')
+    multiprocessing.set_start_method('forkserver')
+    multiprocessing.set_forkserver_preload([modname])
+    for _ in range(2):
+        p = multiprocessing.Process()
+        p.start()
+        p.join()
+elif modname not in sys.modules:
+    raise AssertionError(f'{modname!r} is not in sys.modules')
diff --git 
a/Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst 
b/Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst
new file mode 100644
index 00000000000000..466ba0d232cd1f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst
@@ -0,0 +1,2 @@
+:mod:`multiprocessing`: Flush ``stdout`` and ``stderr`` after preloading
+modules in the ``forkserver``.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to