Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Lib/multiprocessing/forkserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6801,6 +6801,38 @@ 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(os.path.join(init_name), "w") as f:
cmd = '''if 1:
import sys
print('stdout', file=sys.stdout)
print('stderr', file=sys.stderr)\n'''
f.write(cmd)

name = os.path.join(os.path.dirname(__file__), 'mp_preload_flush.py')
env = {'PYTHONPATH': self._temp_dir}
rc, out, err = test.support.script_helper.assert_python_ok(name, **env)
if rc:
support.print_warning("preload flush test failed with stderr:")
support.print_warning(err.decode())
self.assertEqual(rc, 0)

# We want to see all the output if it isn't as expected
self.maxDiff = None
self.assertEqual(err.decode().rstrip(), 'stderr')
self.assertEqual(out.decode().rstrip(), 'stdout')


class MiscTestCase(unittest.TestCase):
def test__all__(self):
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/mp_preload_flush.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import multiprocessing
import sys

modname = 'preloaded_module'
if __name__ == '__main__':
assert modname 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()
else:
assert modname in sys.modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flush ``stdout`` and ``stderr`` after preloading modules in the
:mod:`multiprocessing` ``forkserver``.
Loading