summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/asyncio/base_subprocess.py13
-rw-r--r--Lib/test/test_asyncio/test_subprocess.py25
-rw-r--r--Misc/NEWS.d/next/Library/2022-07-08-08-39-35.gh-issue-88050.0aOC_m.rst1
3 files changed, 33 insertions, 6 deletions
diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py
index 14d5051..c2ca4a2 100644
--- a/Lib/asyncio/base_subprocess.py
+++ b/Lib/asyncio/base_subprocess.py
@@ -215,14 +215,10 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
# 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.
@@ -247,6 +243,11 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
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
diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py
index 961c463..9bc60b9 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -1,4 +1,5 @@
import os
+import shutil
import signal
import sys
import unittest
@@ -182,6 +183,30 @@ class SubprocessMixin:
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(
diff --git a/Misc/NEWS.d/next/Library/2022-07-08-08-39-35.gh-issue-88050.0aOC_m.rst b/Misc/NEWS.d/next/Library/2022-07-08-08-39-35.gh-issue-88050.0aOC_m.rst
new file mode 100644
index 0000000..43c0765
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-07-08-08-39-35.gh-issue-88050.0aOC_m.rst
@@ -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.