summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2023-12-15 14:57:49 (GMT)
committerGitHub <noreply@github.com>2023-12-15 14:57:49 (GMT)
commit4026ad5b2c595b855a3605420cfa0e3d49e63db7 (patch)
tree2698e0d343a0e7488ee397ce93aff6284cc5edd9
parentd1a2adfb0820ee730fa3e4bbc4bd88a67aa50666 (diff)
downloadcpython-4026ad5b2c595b855a3605420cfa0e3d49e63db7.zip
cpython-4026ad5b2c595b855a3605420cfa0e3d49e63db7.tar.gz
cpython-4026ad5b2c595b855a3605420cfa0e3d49e63db7.tar.bz2
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.
-rw-r--r--Lib/multiprocessing/popen_spawn_win32.py54
-rw-r--r--Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst5
2 files changed, 35 insertions, 24 deletions
diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py
index af04430..49d4c7e 100644
--- a/Lib/multiprocessing/popen_spawn_win32.py
+++ b/Lib/multiprocessing/popen_spawn_win32.py
@@ -101,18 +101,20 @@ class Popen(object):
return reduction.duplicate(handle, self.sentinel)
def wait(self, timeout=None):
- if self.returncode is None:
- if timeout is None:
- msecs = _winapi.INFINITE
- else:
- msecs = max(0, int(timeout * 1000 + 0.5))
-
- res = _winapi.WaitForSingleObject(int(self._handle), msecs)
- if res == _winapi.WAIT_OBJECT_0:
- code = _winapi.GetExitCodeProcess(self._handle)
- if code == TERMINATE:
- code = -signal.SIGTERM
- self.returncode = code
+ if self.returncode is not None:
+ return self.returncode
+
+ if timeout is None:
+ msecs = _winapi.INFINITE
+ else:
+ msecs = max(0, int(timeout * 1000 + 0.5))
+
+ res = _winapi.WaitForSingleObject(int(self._handle), msecs)
+ if res == _winapi.WAIT_OBJECT_0:
+ code = _winapi.GetExitCodeProcess(self._handle)
+ if code == TERMINATE:
+ code = -signal.SIGTERM
+ self.returncode = code
return self.returncode
@@ -120,18 +122,22 @@ class Popen(object):
return self.wait(timeout=0)
def terminate(self):
- if self.returncode is None:
- try:
- _winapi.TerminateProcess(int(self._handle), TERMINATE)
- except PermissionError:
- # ERROR_ACCESS_DENIED (winerror 5) is received when the
- # process already died.
- code = _winapi.GetExitCodeProcess(int(self._handle))
- if code == _winapi.STILL_ACTIVE:
- raise
- self.returncode = code
- else:
- self.returncode = -signal.SIGTERM
+ if self.returncode is not None:
+ return
+
+ try:
+ _winapi.TerminateProcess(int(self._handle), TERMINATE)
+ except PermissionError:
+ # ERROR_ACCESS_DENIED (winerror 5) is received when the
+ # process already died.
+ code = _winapi.GetExitCodeProcess(int(self._handle))
+ if code == _winapi.STILL_ACTIVE:
+ raise
+
+ # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess()
+ # returns an exit code different than STILL_ACTIVE, the process can
+ # still be running. Only set self.returncode once WaitForSingleObject()
+ # returns WAIT_OBJECT_0 in wait().
kill = terminate
diff --git a/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst b/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst
new file mode 100644
index 0000000..6fd7f7f
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst
@@ -0,0 +1,5 @@
+:mod:`multiprocessing`: On Windows, fix a race condition in
+``Process.terminate()``: no longer set 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``. Patch by Victor Stinner.