Skip to content

Commit 55f24b1

Browse files
committed
gh-113009: Fix multiprocessing Process.terminate() on Windows
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 fb4cb7c commit 55f24b1

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)