]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-135335: flush stdout/stderr in forkserver after preloading modules (#135338)
authorDuane Griffin <duaneg@dghda.com>
Wed, 18 Jun 2025 12:17:02 +0000 (00:17 +1200)
committerGitHub <noreply@github.com>
Wed, 18 Jun 2025 12:17:02 +0000 (14:17 +0200)
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.

Co-authored-by: Mikhail Efimov <efimov.mikhail@gmail.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
Lib/multiprocessing/forkserver.py
Lib/test/_test_multiprocessing.py
Lib/test/mp_preload_flush.py [new file with mode: 0644]
Misc/NEWS.d/next/Library/2025-06-10-21-42-04.gh-issue-135335.WnUqb_.rst [new file with mode: 0644]

index 681af2610e9b37da648327af1a26a57a308b7acd..c91891ff162c2d4fdd73a1122b9179c2b6c652d1 100644 (file)
@@ -222,6 +222,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()
index 75f31d858d3306c076819fbaa8cbdac4ae1f794f..a1259ff1d63d18fe803d6856013262b50103a4f0 100644 (file)
@@ -6801,6 +6801,35 @@ class _TestSpawnedSysPath(BaseTestCase):
         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 (file)
index 0000000..3501554
--- /dev/null
@@ -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 (file)
index 0000000..466ba0d
--- /dev/null
@@ -0,0 +1,2 @@
+:mod:`multiprocessing`: Flush ``stdout`` and ``stderr`` after preloading
+modules in the ``forkserver``.