Fix a race condition in Popen of
multiprocessing.popen_spawn_win32. The child process now duplicates
the read end of pipe instead of "stealing" it.
Previously, the read end of pipe was "stolen" by the child process,
but it leaked a handle if the child process had been terminated
before it could steal the handle from the parent process.
(cherry picked from commit
2cc9d21fffb8146d30e6fb4221e32410ba4b4ab7)
Co-authored-by: Victor Stinner <vstinner@redhat.com>
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
+
+def _close_handles(*handles):
+ for handle in handles:
+ _winapi.CloseHandle(handle)
+
+
#
# We define a Popen class similar to the one from subprocess, but
# whose constructor takes a process object as its argument.
def __init__(self, process_obj):
prep_data = spawn.get_preparation_data(process_obj._name)
- # read end of pipe will be "stolen" by the child process
+ # read end of pipe will be duplicated by the child process
# -- see spawn_main() in spawn.py.
+ #
+ # bpo-33929: Previously, the read end of pipe was "stolen" by the child
+ # process, but it leaked a handle if the child process had been
+ # terminated before it could steal the handle from the parent process.
rhandle, whandle = _winapi.CreatePipe(None, 0)
wfd = msvcrt.open_osfhandle(whandle, 0)
cmd = spawn.get_command_line(parent_pid=os.getpid(),
self.returncode = None
self._handle = hp
self.sentinel = int(hp)
- self.finalizer = util.Finalize(self, _winapi.CloseHandle, (self.sentinel,))
+ self.finalizer = util.Finalize(self, _close_handles,
+ (self.sentinel, int(rhandle)))
# send information to child
set_spawning_popen(self)
__all__ += ['DupHandle', 'duplicate', 'steal_handle']
import _winapi
- def duplicate(handle, target_process=None, inheritable=False):
+ def duplicate(handle, target_process=None, inheritable=False,
+ *, source_process=None):
'''Duplicate a handle. (target_process is a handle not a pid!)'''
+ current_process = _winapi.GetCurrentProcess()
+ if source_process is None:
+ source_process = current_process
if target_process is None:
- target_process = _winapi.GetCurrentProcess()
+ target_process = current_process
return _winapi.DuplicateHandle(
- _winapi.GetCurrentProcess(), handle, target_process,
+ source_process, handle, target_process,
0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)
def steal_handle(source_pid, handle):
assert is_forking(sys.argv), "Not forking"
if sys.platform == 'win32':
import msvcrt
- new_handle = reduction.steal_handle(parent_pid, pipe_handle)
+ import _winapi
+
+ if parent_pid is not None:
+ source_process = _winapi.OpenProcess(
+ _winapi.PROCESS_DUP_HANDLE, False, parent_pid)
+ else:
+ source_process = None
+ new_handle = reduction.duplicate(pipe_handle,
+ source_process=source_process)
fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
else:
from . import semaphore_tracker
--- /dev/null
+multiprocessing: Fix a race condition in Popen of
+multiprocessing.popen_spawn_win32. The child process now duplicates the read
+end of pipe instead of "stealing" it. Previously, the read end of pipe was
+"stolen" by the child process, but it leaked a handle if the child process had
+been terminated before it could steal the handle from the parent process.