Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1f9a835

Browse files
committed
Issue #14252: Fix subprocess.Popen.terminate() to not raise an error under Windows when the child process has already exited.
1 parent 328dd0d commit 1f9a835

4 files changed

Lines changed: 82 additions & 1 deletion

File tree

Lib/subprocess.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,7 +1075,17 @@ def send_signal(self, sig):
10751075
def terminate(self):
10761076
"""Terminates the process
10771077
"""
1078-
_subprocess.TerminateProcess(self._handle, 1)
1078+
try:
1079+
_subprocess.TerminateProcess(self._handle, 1)
1080+
except OSError as e:
1081+
# ERROR_ACCESS_DENIED (winerror 5) is received when the
1082+
# process already died.
1083+
if e.winerror != 5:
1084+
raise
1085+
rc = _subprocess.GetExitCodeProcess(self._handle)
1086+
if rc == _subprocess.STILL_ACTIVE:
1087+
raise
1088+
self.returncode = rc
10791089

10801090
kill = terminate
10811091

Lib/test/test_subprocess.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,27 @@ def _kill_process(self, method, *args):
989989
getattr(p, method)(*args)
990990
return p
991991

992+
def _kill_dead_process(self, method, *args):
993+
# Do not inherit file handles from the parent.
994+
# It should fix failures on some platforms.
995+
p = subprocess.Popen([sys.executable, "-c", """if 1:
996+
import sys, time
997+
sys.stdout.write('x\\n')
998+
sys.stdout.flush()
999+
"""],
1000+
close_fds=True,
1001+
stdin=subprocess.PIPE,
1002+
stdout=subprocess.PIPE,
1003+
stderr=subprocess.PIPE)
1004+
# Wait for the interpreter to be completely initialized before
1005+
# sending any signal.
1006+
p.stdout.read(1)
1007+
# The process should end after this
1008+
time.sleep(1)
1009+
# This shouldn't raise even though the child is now dead
1010+
getattr(p, method)(*args)
1011+
p.communicate()
1012+
9921013
def test_send_signal(self):
9931014
p = self._kill_process('send_signal', signal.SIGINT)
9941015
_, stderr = p.communicate()
@@ -1007,6 +1028,18 @@ def test_terminate(self):
10071028
self.assertStderrEqual(stderr, b'')
10081029
self.assertEqual(p.wait(), -signal.SIGTERM)
10091030

1031+
def test_send_signal_dead(self):
1032+
# Sending a signal to a dead process
1033+
self._kill_dead_process('send_signal', signal.SIGINT)
1034+
1035+
def test_kill_dead(self):
1036+
# Killing a dead process
1037+
self._kill_dead_process('kill')
1038+
1039+
def test_terminate_dead(self):
1040+
# Terminating a dead process
1041+
self._kill_dead_process('terminate')
1042+
10101043
def check_close_std_fds(self, fds):
10111044
# Issue #9905: test that subprocess pipes still work properly with
10121045
# some standard fds closed
@@ -1568,6 +1601,31 @@ def _kill_process(self, method, *args):
15681601
returncode = p.wait()
15691602
self.assertNotEqual(returncode, 0)
15701603

1604+
def _kill_dead_process(self, method, *args):
1605+
p = subprocess.Popen([sys.executable, "-c", """if 1:
1606+
import sys, time
1607+
sys.stdout.write('x\\n')
1608+
sys.stdout.flush()
1609+
sys.exit(42)
1610+
"""],
1611+
stdin=subprocess.PIPE,
1612+
stdout=subprocess.PIPE,
1613+
stderr=subprocess.PIPE)
1614+
self.addCleanup(p.stdout.close)
1615+
self.addCleanup(p.stderr.close)
1616+
self.addCleanup(p.stdin.close)
1617+
# Wait for the interpreter to be completely initialized before
1618+
# sending any signal.
1619+
p.stdout.read(1)
1620+
# The process should end after this
1621+
time.sleep(1)
1622+
# This shouldn't raise even though the child is now dead
1623+
getattr(p, method)(*args)
1624+
_, stderr = p.communicate()
1625+
self.assertStderrEqual(stderr, b'')
1626+
rc = p.wait()
1627+
self.assertEqual(rc, 42)
1628+
15711629
def test_send_signal(self):
15721630
self._kill_process('send_signal', signal.SIGTERM)
15731631

@@ -1577,6 +1635,15 @@ def test_kill(self):
15771635
def test_terminate(self):
15781636
self._kill_process('terminate')
15791637

1638+
def test_send_signal_dead(self):
1639+
self._kill_dead_process('send_signal', signal.SIGTERM)
1640+
1641+
def test_kill_dead(self):
1642+
self._kill_dead_process('kill')
1643+
1644+
def test_terminate_dead(self):
1645+
self._kill_dead_process('terminate')
1646+
15801647

15811648
# The module says:
15821649
# "NB This only works (and is only relevant) for UNIX."

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Core and Builtins
2222
Library
2323
-------
2424

25+
- Issue #14252: Fix subprocess.Popen.terminate() to not raise an error under
26+
Windows when the child process has already exited.
27+
2528
- Issue #14195: An issue that caused weakref.WeakSet instances to incorrectly
2629
return True for a WeakSet instance 'a' in both 'a < a' and 'a > a' has been
2730
fixed.

PC/_subprocess.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ PyInit__subprocess()
684684
defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0);
685685
defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE);
686686
defint(d, "CREATE_NEW_PROCESS_GROUP", CREATE_NEW_PROCESS_GROUP);
687+
defint(d, "STILL_ACTIVE", STILL_ACTIVE);
687688

688689
return m;
689690
}

0 commit comments

Comments
 (0)