| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,7 @@ def __init__(self, loop, protocol, args, shell, | |||
| 26 | 26 | self._pending_calls = collections.deque() | |
| 27 | 27 | self._pipes = {} | |
| 28 | 28 | self._finished = False | |
| 29 | + self._pipes_connected = False | ||
| 29 | 30 | ||
| 30 | 31 | if stdin == subprocess.PIPE: | |
| 31 | 32 | self._pipes[0] = None | |
@@ -213,6 +214,7 @@ async def _connect_pipes(self, waiter): | |||
| 213 | 214 | else: | |
| 214 | 215 | if waiter is not None and not waiter.cancelled(): | |
| 215 | 216 | waiter.set_result(None) | |
| 217 | + self._pipes_connected = True | ||
| 216 | 218 | ||
| 217 | 219 | def _call(self, cb, *data): | |
| 218 | 220 | if self._pending_calls is not None: | |
@@ -256,6 +258,15 @@ def _try_finish(self): | |||
| 256 | 258 | assert not self._finished | |
| 257 | 259 | if self._returncode is None: | |
| 258 | 260 | return | |
| 261 | + if not self._pipes_connected: | ||
| 262 | + # self._pipes_connected can be False if not all pipes were connected | ||
| 263 | + # because either the process failed to start or the self._connect_pipes task | ||
| 264 | + # got cancelled. In this broken state we consider all pipes disconnected and | ||
| 265 | + # to avoid hanging forever in self._wait as otherwise _exit_waiters | ||
| 266 | + # would never be woken up, we wake them up here. | ||
| 267 | + for waiter in self._exit_waiters: | ||
| 268 | + if not waiter.cancelled(): | ||
| 269 | + waiter.set_result(self._returncode) | ||
| 259 | 270 | if all(p is not None and p.disconnected | |
| 260 | 271 | for p in self._pipes.values()): | |
| 261 | 272 | self._finished = True | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,7 +11,7 @@ | |||
| 11 | 11 | from asyncio import subprocess | |
| 12 | 12 | from test.test_asyncio import utils as test_utils | |
| 13 | 13 | from test import support | |
| 14 | - from test.support import os_helper | ||
| 14 | + from test.support import os_helper, warnings_helper, gc_collect | ||
| 15 | 15 | ||
| 16 | 16 | if not support.has_subprocess_support: | |
| 17 | 17 | raise unittest.SkipTest("test module requires subprocess") | |
@@ -879,6 +879,44 @@ async def main(): | |||
| 879 | 879 | ||
| 880 | 880 | self.loop.run_until_complete(main()) | |
| 881 | 881 | ||
| 882 | + @warnings_helper.ignore_warnings(category=ResourceWarning) | ||
| 883 | + def test_subprocess_read_pipe_cancelled(self): | ||
| 884 | + async def main(): | ||
| 885 | + loop = asyncio.get_running_loop() | ||
| 886 | + loop.connect_read_pipe = mock.AsyncMock(side_effect=asyncio.CancelledError) | ||
| 887 | + with self.assertRaises(asyncio.CancelledError): | ||
| 888 | + await asyncio.create_subprocess_exec(*PROGRAM_BLOCKED, stderr=asyncio.subprocess.PIPE) | ||
| 889 | + | ||
| 890 | + asyncio.run(main()) | ||
| 891 | + gc_collect() | ||
| 892 | + | ||
| 893 | + @warnings_helper.ignore_warnings(category=ResourceWarning) | ||
| 894 | + def test_subprocess_write_pipe_cancelled(self): | ||
| 895 | + async def main(): | ||
| 896 | + loop = asyncio.get_running_loop() | ||
| 897 | + loop.connect_write_pipe = mock.AsyncMock(side_effect=asyncio.CancelledError) | ||
| 898 | + with self.assertRaises(asyncio.CancelledError): | ||
| 899 | + await asyncio.create_subprocess_exec(*PROGRAM_BLOCKED, stdin=asyncio.subprocess.PIPE) | ||
| 900 | + | ||
| 901 | + asyncio.run(main()) | ||
| 902 | + gc_collect() | ||
| 903 | + | ||
| 904 | + @warnings_helper.ignore_warnings(category=ResourceWarning) | ||
| 905 | + def test_subprocess_read_write_pipe_cancelled(self): | ||
| 906 | + async def main(): | ||
| 907 | + loop = asyncio.get_running_loop() | ||
| 908 | + loop.connect_read_pipe = mock.AsyncMock(side_effect=asyncio.CancelledError) | ||
| 909 | + loop.connect_write_pipe = mock.AsyncMock(side_effect=asyncio.CancelledError) | ||
| 910 | + with self.assertRaises(asyncio.CancelledError): | ||
| 911 | + await asyncio.create_subprocess_exec( | ||
| 912 | + *PROGRAM_BLOCKED, | ||
| 913 | + stdin=asyncio.subprocess.PIPE, | ||
| 914 | + stdout=asyncio.subprocess.PIPE, | ||
| 915 | + stderr=asyncio.subprocess.PIPE, | ||
| 916 | + ) | ||
| 917 | + | ||
| 918 | + asyncio.run(main()) | ||
| 919 | + gc_collect() | ||
| 882 | 920 | ||
| 883 | 921 | if sys.platform != 'win32': | |
| 884 | 922 | # Unix | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + Fix hang when cancelling process created by :func:`asyncio.create_subprocess_exec` or :func:`asyncio.create_subprocess_shell`. Patch by Kumar Aditya. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments