Skip to content

Commit 4e0fda5

Browse files
miss-islingtonzooba
authored andcommitted
gh-98360: multiprocessing now spawns children on Windows with correct argv[0] in virtual environments (GH-98462)
(cherry picked from commit e48f9b2) Co-authored-by: Steve Dower <[email protected]>
1 parent 4c0c1e2 commit 4e0fda5

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

Lib/multiprocessing/popen_spawn_win32.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,20 @@ def __init__(self, process_obj):
5454
wfd = msvcrt.open_osfhandle(whandle, 0)
5555
cmd = spawn.get_command_line(parent_pid=os.getpid(),
5656
pipe_handle=rhandle)
57-
cmd = ' '.join('"%s"' % x for x in cmd)
5857

5958
python_exe = spawn.get_executable()
6059

6160
# bpo-35797: When running in a venv, we bypass the redirect
6261
# executor and launch our base Python.
6362
if WINENV and _path_eq(python_exe, sys.executable):
64-
python_exe = sys._base_executable
63+
cmd[0] = python_exe = sys._base_executable
6564
env = os.environ.copy()
6665
env["__PYVENV_LAUNCHER__"] = sys.executable
6766
else:
6867
env = None
6968

69+
cmd = ' '.join('"%s"' % x for x in cmd)
70+
7071
with open(wfd, 'wb', closefd=True) as to_child:
7172
# start process
7273
try:
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import multiprocessing
2+
import random
3+
import sys
4+
import time
5+
6+
def fill_queue(queue, code):
7+
queue.put(code)
8+
9+
10+
def drain_queue(queue, code):
11+
if code != queue.get():
12+
sys.exit(1)
13+
14+
15+
def test_func():
16+
code = random.randrange(0, 1000)
17+
queue = multiprocessing.Queue()
18+
fill_pool = multiprocessing.Process(
19+
target=fill_queue,
20+
args=(queue, code)
21+
)
22+
drain_pool = multiprocessing.Process(
23+
target=drain_queue,
24+
args=(queue, code)
25+
)
26+
drain_pool.start()
27+
fill_pool.start()
28+
fill_pool.join()
29+
drain_pool.join()
30+
31+
32+
def main():
33+
test_pool = multiprocessing.Process(target=test_func)
34+
test_pool.start()
35+
test_pool.join()
36+
sys.exit(test_pool.exitcode)
37+
38+
39+
if __name__ == "__main__":
40+
main()

Lib/test/test_venv.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from test.support import (captured_stdout, captured_stderr, requires_zlib,
2121
skip_if_broken_multiprocessing_synchronize, verbose,
2222
requires_subprocess, is_emscripten, is_wasi,
23-
requires_venv_with_pip)
23+
requires_venv_with_pip, TEST_HOME_DIR)
2424
from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree)
2525
import unittest
2626
import venv
@@ -482,6 +482,20 @@ def test_multiprocessing(self):
482482
'pool.terminate()'])
483483
self.assertEqual(out.strip(), "python".encode())
484484

485+
@requireVenvCreate
486+
def test_multiprocessing_recursion(self):
487+
"""
488+
Test that the multiprocessing is able to spawn itself
489+
"""
490+
skip_if_broken_multiprocessing_synchronize()
491+
492+
rmtree(self.env_dir)
493+
self.run_with_capture(venv.create, self.env_dir)
494+
envpy = os.path.join(os.path.realpath(self.env_dir),
495+
self.bindir, self.exe)
496+
script = os.path.join(TEST_HOME_DIR, '_test_venv_multiprocessing.py')
497+
subprocess.check_call([envpy, script])
498+
485499
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
486500
def test_deactivate_with_strict_bash_opts(self):
487501
bash = shutil.which("bash")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixes :mod:`multiprocessing` spawning child processes on Windows from a
2+
virtual environment to ensure that child processes that also use
3+
:mod:`multiprocessing` to spawn more children will recognize that they are
4+
in a virtual environment.

0 commit comments

Comments
 (0)