Skip to content

Commit

Permalink
gh-88050: Fix asyncio subprocess to kill process cleanly when process…
Browse files Browse the repository at this point in the history
… is blocked (GH-32073)

(cherry picked from commit 7015e13)

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
  • Loading branch information
miss-islington and kumaraditya303 authored Oct 5, 2022
1 parent 5c8aa2e commit aec1333
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 6 deletions.
13 changes: 7 additions & 6 deletions Lib/asyncio/base_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,10 @@ def _process_exited(self, returncode):
# object. On Python 3.6, it is required to avoid a ResourceWarning.
self._proc.returncode = returncode
self._call(self._protocol.process_exited)
for p in self._pipes.values():
p.pipe.close()
self._try_finish()

# wake up futures waiting for wait()
for waiter in self._exit_waiters:
if not waiter.cancelled():
waiter.set_result(returncode)
self._exit_waiters = None

async def _wait(self):
"""Wait until the process exit and return the process return code.
Expand All @@ -247,6 +243,11 @@ def _call_connection_lost(self, exc):
try:
self._protocol.connection_lost(exc)
finally:
# wake up futures waiting for wait()
for waiter in self._exit_waiters:
if not waiter.cancelled():
waiter.set_result(self._returncode)
self._exit_waiters = None
self._loop = None
self._proc = None
self._protocol = None
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_asyncio/test_subprocess.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import shutil
import signal
import sys
import unittest
Expand Down Expand Up @@ -182,6 +183,30 @@ def test_kill(self):
else:
self.assertEqual(-signal.SIGKILL, returncode)

def test_kill_issue43884(self):
blocking_shell_command = f'{sys.executable} -c "import time; time.sleep(100000000)"'
creationflags = 0
if sys.platform == 'win32':
from subprocess import CREATE_NEW_PROCESS_GROUP
# On windows create a new process group so that killing process
# kills the process and all its children.
creationflags = CREATE_NEW_PROCESS_GROUP
proc = self.loop.run_until_complete(
asyncio.create_subprocess_shell(blocking_shell_command, stdout=asyncio.subprocess.PIPE,
creationflags=creationflags)
)
self.loop.run_until_complete(asyncio.sleep(1))
if sys.platform == 'win32':
proc.send_signal(signal.CTRL_BREAK_EVENT)
# On windows it is an alias of terminate which sets the return code
proc.kill()
returncode = self.loop.run_until_complete(proc.wait())
if sys.platform == 'win32':
self.assertIsInstance(returncode, int)
# expect 1 but sometimes get 0
else:
self.assertEqual(-signal.SIGKILL, returncode)

def test_terminate(self):
args = PROGRAM_BLOCKED
proc = self.loop.run_until_complete(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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 comments on commit aec1333

Please sign in to comment.