Skip to content

Commit 485b26b

Browse files
bpo-43884: Fix asyncio subprocess kill process cleanly when process is blocked
1 parent 3ac4e78 commit 485b26b

File tree

2 files changed

+30
-7
lines changed

2 files changed

+30
-7
lines changed

Lib/asyncio/base_subprocess.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,22 @@ def _process_exited(self, returncode):
214214
# asyncio uses a child watcher: copy the status into the Popen
215215
# object. On Python 3.6, it is required to avoid a ResourceWarning.
216216
self._proc.returncode = returncode
217-
self._call(self._protocol.process_exited)
218-
self._try_finish()
219217

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
218+
def __process_exited():
219+
self._protocol.process_exited()
220+
# Since process exited, clear all pipes
221+
for proto in self._pipes.values():
222+
if proto is None:
223+
continue
224+
proto.pipe.close()
225+
# Call _wait waiters
226+
for waiter in self._exit_waiters:
227+
if not waiter.cancelled():
228+
waiter.set_result(self._returncode)
229+
self._exit_waiters = None
230+
231+
self._call(__process_exited)
232+
self._try_finish()
225233

226234
async def _wait(self):
227235
"""Wait until the process exit and return the process return code.
@@ -252,6 +260,7 @@ def _call_connection_lost(self, exc):
252260
self._protocol = None
253261

254262

263+
255264
class WriteSubprocessPipeProto(protocols.BaseProtocol):
256265

257266
def __init__(self, proc, fd):

Lib/test/test_asyncio/test_subprocess.py

+14
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,20 @@ def test_kill(self):
181181
else:
182182
self.assertEqual(-signal.SIGKILL, returncode)
183183

184+
def test_kill_issue43884(self):
185+
blocking_shell_command = f'{sys.executable} -c "import time; time.sleep(10000)"'
186+
proc = self.loop.run_until_complete(
187+
asyncio.create_subprocess_shell(blocking_shell_command, stdout=asyncio.subprocess.PIPE)
188+
)
189+
self.loop.run_until_complete(asyncio.sleep(1))
190+
proc.kill()
191+
returncode = self.loop.run_until_complete(proc.wait())
192+
if sys.platform == 'win32':
193+
self.assertIsInstance(returncode, int)
194+
# expect 1 but sometimes get 0
195+
else:
196+
self.assertEqual(-signal.SIGKILL, returncode)
197+
184198
def test_terminate(self):
185199
args = PROGRAM_BLOCKED
186200
proc = self.loop.run_until_complete(

0 commit comments

Comments
 (0)