Commit 1f9a8354 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #14252: Fix subprocess.Popen.terminate() to not raise an error under...

Issue #14252: Fix subprocess.Popen.terminate() to not raise an error under Windows when the child process has already exited.
parent 328dd0d5
...@@ -1075,7 +1075,17 @@ class Popen(object): ...@@ -1075,7 +1075,17 @@ class Popen(object):
def terminate(self): def terminate(self):
"""Terminates the process """Terminates the process
""" """
try:
_subprocess.TerminateProcess(self._handle, 1) _subprocess.TerminateProcess(self._handle, 1)
except OSError as e:
# ERROR_ACCESS_DENIED (winerror 5) is received when the
# process already died.
if e.winerror != 5:
raise
rc = _subprocess.GetExitCodeProcess(self._handle)
if rc == _subprocess.STILL_ACTIVE:
raise
self.returncode = rc
kill = terminate kill = terminate
......
...@@ -989,6 +989,27 @@ class POSIXProcessTestCase(BaseTestCase): ...@@ -989,6 +989,27 @@ class POSIXProcessTestCase(BaseTestCase):
getattr(p, method)(*args) getattr(p, method)(*args)
return p return p
def _kill_dead_process(self, method, *args):
# Do not inherit file handles from the parent.
# It should fix failures on some platforms.
p = subprocess.Popen([sys.executable, "-c", """if 1:
import sys, time
sys.stdout.write('x\\n')
sys.stdout.flush()
"""],
close_fds=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# Wait for the interpreter to be completely initialized before
# sending any signal.
p.stdout.read(1)
# The process should end after this
time.sleep(1)
# This shouldn't raise even though the child is now dead
getattr(p, method)(*args)
p.communicate()
def test_send_signal(self): def test_send_signal(self):
p = self._kill_process('send_signal', signal.SIGINT) p = self._kill_process('send_signal', signal.SIGINT)
_, stderr = p.communicate() _, stderr = p.communicate()
...@@ -1007,6 +1028,18 @@ class POSIXProcessTestCase(BaseTestCase): ...@@ -1007,6 +1028,18 @@ class POSIXProcessTestCase(BaseTestCase):
self.assertStderrEqual(stderr, b'') self.assertStderrEqual(stderr, b'')
self.assertEqual(p.wait(), -signal.SIGTERM) self.assertEqual(p.wait(), -signal.SIGTERM)
def test_send_signal_dead(self):
# Sending a signal to a dead process
self._kill_dead_process('send_signal', signal.SIGINT)
def test_kill_dead(self):
# Killing a dead process
self._kill_dead_process('kill')
def test_terminate_dead(self):
# Terminating a dead process
self._kill_dead_process('terminate')
def check_close_std_fds(self, fds): def check_close_std_fds(self, fds):
# Issue #9905: test that subprocess pipes still work properly with # Issue #9905: test that subprocess pipes still work properly with
# some standard fds closed # some standard fds closed
...@@ -1568,6 +1601,31 @@ class Win32ProcessTestCase(BaseTestCase): ...@@ -1568,6 +1601,31 @@ class Win32ProcessTestCase(BaseTestCase):
returncode = p.wait() returncode = p.wait()
self.assertNotEqual(returncode, 0) self.assertNotEqual(returncode, 0)
def _kill_dead_process(self, method, *args):
p = subprocess.Popen([sys.executable, "-c", """if 1:
import sys, time
sys.stdout.write('x\\n')
sys.stdout.flush()
sys.exit(42)
"""],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
self.addCleanup(p.stdout.close)
self.addCleanup(p.stderr.close)
self.addCleanup(p.stdin.close)
# Wait for the interpreter to be completely initialized before
# sending any signal.
p.stdout.read(1)
# The process should end after this
time.sleep(1)
# This shouldn't raise even though the child is now dead
getattr(p, method)(*args)
_, stderr = p.communicate()
self.assertStderrEqual(stderr, b'')
rc = p.wait()
self.assertEqual(rc, 42)
def test_send_signal(self): def test_send_signal(self):
self._kill_process('send_signal', signal.SIGTERM) self._kill_process('send_signal', signal.SIGTERM)
...@@ -1577,6 +1635,15 @@ class Win32ProcessTestCase(BaseTestCase): ...@@ -1577,6 +1635,15 @@ class Win32ProcessTestCase(BaseTestCase):
def test_terminate(self): def test_terminate(self):
self._kill_process('terminate') self._kill_process('terminate')
def test_send_signal_dead(self):
self._kill_dead_process('send_signal', signal.SIGTERM)
def test_kill_dead(self):
self._kill_dead_process('kill')
def test_terminate_dead(self):
self._kill_dead_process('terminate')
# The module says: # The module says:
# "NB This only works (and is only relevant) for UNIX." # "NB This only works (and is only relevant) for UNIX."
......
...@@ -22,6 +22,9 @@ Core and Builtins ...@@ -22,6 +22,9 @@ Core and Builtins
Library Library
------- -------
- Issue #14252: Fix subprocess.Popen.terminate() to not raise an error under
Windows when the child process has already exited.
- Issue #14195: An issue that caused weakref.WeakSet instances to incorrectly - Issue #14195: An issue that caused weakref.WeakSet instances to incorrectly
return True for a WeakSet instance 'a' in both 'a < a' and 'a > a' has been return True for a WeakSet instance 'a' in both 'a < a' and 'a > a' has been
fixed. fixed.
......
...@@ -684,6 +684,7 @@ PyInit__subprocess() ...@@ -684,6 +684,7 @@ PyInit__subprocess()
defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0); defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0);
defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE); defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE);
defint(d, "CREATE_NEW_PROCESS_GROUP", CREATE_NEW_PROCESS_GROUP); defint(d, "CREATE_NEW_PROCESS_GROUP", CREATE_NEW_PROCESS_GROUP);
defint(d, "STILL_ACTIVE", STILL_ACTIVE);
return m; return m;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment