Skip to content

Commit 7015e13

Browse files
pythongh-88050: Fix asyncio subprocess to kill process cleanly when process is blocked (python#32073)
1 parent 0e72606 commit 7015e13

File tree

3 files changed

+33
-6
lines changed

3 files changed

+33
-6
lines changed

Lib/asyncio/base_subprocess.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,10 @@ def _process_exited(self, returncode):
215215
# object. On Python 3.6, it is required to avoid a ResourceWarning.
216216
self._proc.returncode = returncode
217217
self._call(self._protocol.process_exited)
218+
for p in self._pipes.values():
219+
p.pipe.close()
218220
self._try_finish()
219221

220-
# wake up futures waiting for wait()
221-
for waiter in self._exit_waiters:
222-
if not waiter.cancelled():
223-
waiter.set_result(returncode)
224-
self._exit_waiters = None
225-
226222
async def _wait(self):
227223
"""Wait until the process exit and return the process return code.
228224
@@ -247,6 +243,11 @@ def _call_connection_lost(self, exc):
247243
try:
248244
self._protocol.connection_lost(exc)
249245
finally:
246+
# wake up futures waiting for wait()
247+
for waiter in self._exit_waiters:
248+
if not waiter.cancelled():
249+
waiter.set_result(self._returncode)
250+
self._exit_waiters = None
250251
self._loop = None
251252
self._proc = None
252253
self._protocol = None

Lib/test/test_asyncio/test_subprocess.py

+25
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import shutil
23
import signal
34
import sys
45
import unittest
@@ -182,6 +183,30 @@ def test_kill(self):
182183
else:
183184
self.assertEqual(-signal.SIGKILL, returncode)
184185

186+
def test_kill_issue43884(self):
187+
blocking_shell_command = f'{sys.executable} -c "import time; time.sleep(100000000)"'
188+
creationflags = 0
189+
if sys.platform == 'win32':
190+
from subprocess import CREATE_NEW_PROCESS_GROUP
191+
# On windows create a new process group so that killing process
192+
# kills the process and all its children.
193+
creationflags = CREATE_NEW_PROCESS_GROUP
194+
proc = self.loop.run_until_complete(
195+
asyncio.create_subprocess_shell(blocking_shell_command, stdout=asyncio.subprocess.PIPE,
196+
creationflags=creationflags)
197+
)
198+
self.loop.run_until_complete(asyncio.sleep(1))
199+
if sys.platform == 'win32':
200+
proc.send_signal(signal.CTRL_BREAK_EVENT)
201+
# On windows it is an alias of terminate which sets the return code
202+
proc.kill()
203+
returncode = self.loop.run_until_complete(proc.wait())
204+
if sys.platform == 'win32':
205+
self.assertIsInstance(returncode, int)
206+
# expect 1 but sometimes get 0
207+
else:
208+
self.assertEqual(-signal.SIGKILL, returncode)
209+
185210
def test_terminate(self):
186211
args = PROGRAM_BLOCKED
187212
proc = self.loop.run_until_complete(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :mod:`asyncio` subprocess transport to kill process cleanly when process is blocked and avoid ``RuntimeError`` when loop is closed. Patch by Kumar Aditya.

0 commit comments

Comments
 (0)