Skip to content

Commit d59b229

Browse files
committed
gh-135335: flush stdout/stderr in forkserver after preloading modules
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.
1 parent 2677dd0 commit d59b229

4 files changed

Lines changed: 36 additions & 0 deletions

File tree

Lib/multiprocessing/forkserver.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None,
222222
except ImportError:
223223
pass
224224

225+
# gh-135335: flush stdout/stderr in case any of the preloaded modules
226+
# wrote to them, otherwise children might inherit buffered data
227+
util._flush_std_streams()
228+
225229
util._close_stdin()
226230

227231
sig_r, sig_w = os.pipe()

Lib/test/_test_multiprocessing.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6801,6 +6801,29 @@ def test_child_sys_path(self):
68016801
self.assertEqual(child_sys_path[1:], sys.path[1:])
68026802
self.assertIsNone(import_error, msg=f"child could not import {self._mod_name}")
68036803

6804+
def test_std_streams_flushed_after_preload(self):
6805+
# gh-135335: Check fork server flushes standard streams after
6806+
# preloading modules
6807+
if multiprocessing.get_start_method() != "forkserver":
6808+
self.skipTest("forkserver specific test")
6809+
6810+
# Create a test module in the temporary directory on the child's path
6811+
# TODO: This can all be simplified once gh-126631 is fixed and we can
6812+
# use __main__ instead of a module.
6813+
os.mkdir(os.path.join(self._temp_dir, 'a'))
6814+
with open(os.path.join(self._temp_dir, 'a', '__init__.py'), "w") as f:
6815+
f.write('''if 1:
6816+
import sys
6817+
print('stdout', file=sys.stdout)
6818+
print('stderr', file=sys.stderr)''')
6819+
6820+
name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py')
6821+
env = {'PYTHONPATH': ":".join(sys.path)}
6822+
rc, out, err = test.support.script_helper.assert_python_ok(name, **env)
6823+
self.assertEqual(rc, 0)
6824+
self.assertEqual(out.decode().rstrip(), 'stdout')
6825+
self.assertEqual(err.decode().rstrip(), 'stderr')
6826+
68046827

68056828
class MiscTestCase(unittest.TestCase):
68066829
def test__all__(self):

Lib/test/mp_preload_flush.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import multiprocessing
2+
if __name__ == '__main__':
3+
multiprocessing.set_forkserver_preload(['a'])
4+
for _ in range(2):
5+
p = multiprocessing.Process()
6+
p.start()
7+
p.join()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Flush ``stdout`` and ``stderr`` after preloading modules in the
2+
:mod:`multiprocessing` ``forkserver``.

0 commit comments

Comments
 (0)