mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
bpo-33929: multiprocessing: fix handle leak on race condition (GH-7921)
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.
This commit is contained in:
parent
f15f66d275
commit
2cc9d21fff
4 changed files with 34 additions and 6 deletions
|
@ -18,6 +18,12 @@ TERMINATE = 0x10000
|
||||||
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
|
WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False))
|
||||||
WINSERVICE = sys.executable.lower().endswith("pythonservice.exe")
|
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
|
# We define a Popen class similar to the one from subprocess, but
|
||||||
# whose constructor takes a process object as its argument.
|
# whose constructor takes a process object as its argument.
|
||||||
|
@ -32,8 +38,12 @@ class Popen(object):
|
||||||
def __init__(self, process_obj):
|
def __init__(self, process_obj):
|
||||||
prep_data = spawn.get_preparation_data(process_obj._name)
|
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.
|
# -- 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)
|
rhandle, whandle = _winapi.CreatePipe(None, 0)
|
||||||
wfd = msvcrt.open_osfhandle(whandle, 0)
|
wfd = msvcrt.open_osfhandle(whandle, 0)
|
||||||
cmd = spawn.get_command_line(parent_pid=os.getpid(),
|
cmd = spawn.get_command_line(parent_pid=os.getpid(),
|
||||||
|
@ -56,7 +66,8 @@ class Popen(object):
|
||||||
self.returncode = None
|
self.returncode = None
|
||||||
self._handle = hp
|
self._handle = hp
|
||||||
self.sentinel = int(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
|
# send information to child
|
||||||
set_spawning_popen(self)
|
set_spawning_popen(self)
|
||||||
|
|
|
@ -68,12 +68,16 @@ if sys.platform == 'win32':
|
||||||
__all__ += ['DupHandle', 'duplicate', 'steal_handle']
|
__all__ += ['DupHandle', 'duplicate', 'steal_handle']
|
||||||
import _winapi
|
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!)'''
|
'''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:
|
if target_process is None:
|
||||||
target_process = _winapi.GetCurrentProcess()
|
target_process = current_process
|
||||||
return _winapi.DuplicateHandle(
|
return _winapi.DuplicateHandle(
|
||||||
_winapi.GetCurrentProcess(), handle, target_process,
|
source_process, handle, target_process,
|
||||||
0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)
|
0, inheritable, _winapi.DUPLICATE_SAME_ACCESS)
|
||||||
|
|
||||||
def steal_handle(source_pid, handle):
|
def steal_handle(source_pid, handle):
|
||||||
|
|
|
@ -96,7 +96,15 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
|
||||||
assert is_forking(sys.argv), "Not forking"
|
assert is_forking(sys.argv), "Not forking"
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
import msvcrt
|
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)
|
fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
|
||||||
else:
|
else:
|
||||||
from . import semaphore_tracker
|
from . import semaphore_tracker
|
||||||
|
|
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue