Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 13 additions & 2 deletions Lib/multiprocessing/popen_spawn_win32.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
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.
Expand All @@ -32,8 +38,12 @@ class Popen(object):
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(),
Expand All @@ -56,7 +66,8 @@ def __init__(self, process_obj):
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)
Expand Down
10 changes: 7 additions & 3 deletions Lib/multiprocessing/reduction.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,16 @@ def dump(obj, file, protocol=None):
__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):
Expand Down
10 changes: 9 additions & 1 deletion Lib/multiprocessing/spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,15 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
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.