From 0ed6e0baf77bf84fa378c18168d53f5748ae7441 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 28 Sep 2023 18:27:14 +0200 Subject: [PATCH] gh-110036: multiprocessing Popen.terminate() catchs PermissionError On Windows, multiprocessing Popen.terminate() now catchs PermissionError and get the process exit code. If the process is still running, raise again the PermissionError. Otherwise, the process terminated as expected: store its exit code. --- Lib/multiprocessing/popen_spawn_win32.py | 11 +++++++++-- Lib/test/_test_multiprocessing.py | 5 +++-- .../2023-09-28-18-53-11.gh-issue-110036.fECxTj.rst | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-09-28-18-53-11.gh-issue-110036.fECxTj.rst diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py index 4d60ffc030bea6..af044305709e56 100644 --- a/Lib/multiprocessing/popen_spawn_win32.py +++ b/Lib/multiprocessing/popen_spawn_win32.py @@ -14,6 +14,7 @@ # # +# Exit code used by Popen.terminate() TERMINATE = 0x10000 WINEXE = (sys.platform == 'win32' and getattr(sys, 'frozen', False)) WINSERVICE = sys.executable.lower().endswith("pythonservice.exe") @@ -122,9 +123,15 @@ def terminate(self): if self.returncode is None: try: _winapi.TerminateProcess(int(self._handle), TERMINATE) - except OSError: - if self.wait(timeout=1.0) is None: + 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 kill = terminate diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 756d6808518fc4..39666dd331db0b 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -557,13 +557,14 @@ def handler(*args): def test_terminate(self): exitcode = self._kill_process(multiprocessing.Process.terminate) - if os.name != 'nt': - self.assertEqual(exitcode, -signal.SIGTERM) + self.assertEqual(exitcode, -signal.SIGTERM) def test_kill(self): exitcode = self._kill_process(multiprocessing.Process.kill) if os.name != 'nt': self.assertEqual(exitcode, -signal.SIGKILL) + else: + self.assertEqual(exitcode, -signal.SIGTERM) def test_cpu_count(self): try: diff --git a/Misc/NEWS.d/next/Library/2023-09-28-18-53-11.gh-issue-110036.fECxTj.rst b/Misc/NEWS.d/next/Library/2023-09-28-18-53-11.gh-issue-110036.fECxTj.rst new file mode 100644 index 00000000000000..ddb11b5c3546a1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-09-28-18-53-11.gh-issue-110036.fECxTj.rst @@ -0,0 +1,5 @@ +On Windows, multiprocessing ``Popen.terminate()`` now catchs +:exc:`PermissionError` and get the process exit code. If the process is +still running, raise again the :exc:`PermissionError`. Otherwise, the +process terminated as expected: store its exit code. Patch by Victor +Stinner.