Skip to content

Commit 4026ad5

Browse files
authored
gh-113009: Fix multiprocessing Process.terminate() on Windows (#113128)
On Windows, Process.terminate() no longer sets the returncode attribute to always call WaitForSingleObject() in Process.wait(). Previously, sometimes the process was still running after TerminateProcess() even if GetExitCodeProcess() is not STILL_ACTIVE.
1 parent d1a2adf commit 4026ad5

File tree

2 files changed

+35
-24
lines changed

2 files changed

+35
-24
lines changed

Lib/multiprocessing/popen_spawn_win32.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -101,37 +101,43 @@ def duplicate_for_child(self, handle):
101101
return reduction.duplicate(handle, self.sentinel)
102102

103103
def wait(self, timeout=None):
104-
if self.returncode is None:
105-
if timeout is None:
106-
msecs = _winapi.INFINITE
107-
else:
108-
msecs = max(0, int(timeout * 1000 + 0.5))
109-
110-
res = _winapi.WaitForSingleObject(int(self._handle), msecs)
111-
if res == _winapi.WAIT_OBJECT_0:
112-
code = _winapi.GetExitCodeProcess(self._handle)
113-
if code == TERMINATE:
114-
code = -signal.SIGTERM
115-
self.returncode = code
104+
if self.returncode is not None:
105+
return self.returncode
106+
107+
if timeout is None:
108+
msecs = _winapi.INFINITE
109+
else:
110+
msecs = max(0, int(timeout * 1000 + 0.5))
111+
112+
res = _winapi.WaitForSingleObject(int(self._handle), msecs)
113+
if res == _winapi.WAIT_OBJECT_0:
114+
code = _winapi.GetExitCodeProcess(self._handle)
115+
if code == TERMINATE:
116+
code = -signal.SIGTERM
117+
self.returncode = code
116118

117119
return self.returncode
118120

119121
def poll(self):
120122
return self.wait(timeout=0)
121123

122124
def terminate(self):
123-
if self.returncode is None:
124-
try:
125-
_winapi.TerminateProcess(int(self._handle), TERMINATE)
126-
except PermissionError:
127-
# ERROR_ACCESS_DENIED (winerror 5) is received when the
128-
# process already died.
129-
code = _winapi.GetExitCodeProcess(int(self._handle))
130-
if code == _winapi.STILL_ACTIVE:
131-
raise
132-
self.returncode = code
133-
else:
134-
self.returncode = -signal.SIGTERM
125+
if self.returncode is not None:
126+
return
127+
128+
try:
129+
_winapi.TerminateProcess(int(self._handle), TERMINATE)
130+
except PermissionError:
131+
# ERROR_ACCESS_DENIED (winerror 5) is received when the
132+
# process already died.
133+
code = _winapi.GetExitCodeProcess(int(self._handle))
134+
if code == _winapi.STILL_ACTIVE:
135+
raise
136+
137+
# gh-113009: Don't set self.returncode. Even if GetExitCodeProcess()
138+
# returns an exit code different than STILL_ACTIVE, the process can
139+
# still be running. Only set self.returncode once WaitForSingleObject()
140+
# returns WAIT_OBJECT_0 in wait().
135141

136142
kill = terminate
137143

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:mod:`multiprocessing`: On Windows, fix a race condition in
2+
``Process.terminate()``: no longer set the ``returncode`` attribute to
3+
always call ``WaitForSingleObject()`` in ``Process.wait()``. Previously,
4+
sometimes the process was still running after ``TerminateProcess()`` even if
5+
``GetExitCodeProcess()`` is not ``STILL_ACTIVE``. Patch by Victor Stinner.

0 commit comments

Comments
 (0)