Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a0a1aa3

Browse files
miss-islingtonduanegefimov-mikhailvstinner
authored
[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 9877d19) Co-authored-by: Duane Griffin <[email protected]> Co-authored-by: Mikhail Efimov <[email protected]> Co-authored-by: Victor Stinner <[email protected]>
1 parent 28a70a5 commit a0a1aa3

File tree

4 files changed

+50
-0
lines changed

4 files changed

+50
-0
lines changed

Lib/multiprocessing/forkserver.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
182182
except ImportError:
183183
pass
184184

185+
# gh-135335: flush stdout/stderr in case any of the preloaded modules
186+
# wrote to them, otherwise children might inherit buffered data
187+
util._flush_std_streams()
188+
185189
util._close_stdin()
186190

187191
sig_r, sig_w = os.pipe()

Lib/test/_test_multiprocessing.py

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

6454+
def test_std_streams_flushed_after_preload(self):
6455+
# gh-135335: Check fork server flushes standard streams after
6456+
# preloading modules
6457+
if multiprocessing.get_start_method() != "forkserver":
6458+
self.skipTest("forkserver specific test")
6459+
6460+
# Create a test module in the temporary directory on the child's path
6461+
# TODO: This can all be simplified once gh-126631 is fixed and we can
6462+
# use __main__ instead of a module.
6463+
dirname = os.path.join(self._temp_dir, 'preloaded_module')
6464+
init_name = os.path.join(dirname, '__init__.py')
6465+
os.mkdir(dirname)
6466+
with open(init_name, "w") as f:
6467+
cmd = '''if 1:
6468+
import sys
6469+
print('stderr', end='', file=sys.stderr)
6470+
print('stdout', end='', file=sys.stdout)
6471+
'''
6472+
f.write(cmd)
6473+
6474+
name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py')
6475+
env = {'PYTHONPATH': self._temp_dir}
6476+
_, out, err = test.support.script_helper.assert_python_ok(name, **env)
6477+
6478+
# Check stderr first, as it is more likely to be useful to see in the
6479+
# event of a failure.
6480+
self.assertEqual(err.decode().rstrip(), 'stderr')
6481+
self.assertEqual(out.decode().rstrip(), 'stdout')
6482+
64546483

64556484
class MiscTestCase(unittest.TestCase):
64566485
def test__all__(self):

Lib/test/mp_preload_flush.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import multiprocessing
2+
import sys
3+
4+
modname = 'preloaded_module'
5+
if __name__ == '__main__':
6+
if modname in sys.modules:
7+
raise AssertionError(f'{modname!r} is not in sys.modules')
8+
multiprocessing.set_start_method('forkserver')
9+
multiprocessing.set_forkserver_preload([modname])
10+
for _ in range(2):
11+
p = multiprocessing.Process()
12+
p.start()
13+
p.join()
14+
elif modname not in sys.modules:
15+
raise AssertionError(f'{modname!r} is not in sys.modules')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`multiprocessing`: Flush ``stdout`` and ``stderr`` after preloading
2+
modules in the ``forkserver``.

0 commit comments

Comments
 (0)