'Asyncio Terminate Subprocess on wait_for timeout
I have a long running process and its children (in this example it is stress) that I wish to terminate after some time. I am using asyncio.wait_for since it's what the documentation suggests, but while the timeout occurs and the asyncio.TimeoutError is raised, the process is still running. I'm running on Python 3.8.10.
Here's my code:
import asyncio
async def run(cmd):
print("Running: ", cmd)
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
stdout = stdout.decode('UTF-8')
stderr = stderr.decode('UTF-8')
return (stdout, stderr, proc.pid, proc.returncode)
async def run_with_timeout(cmd, timeout=20):
task = asyncio.wait_for(run(cmd), timeout=timeout)
try:
output = await task
stdout, _, pid, _ = output
return str(stdout).strip()
except asyncio.TimeoutError as e:
print("Terminating Process '{0}' (timed out)".format(cmd))
asyncio.run(run_with_timeout(['stress', '--cpu', '2'], timeout=5))
Can someone suggest a way to kill this process after the timeout? Thanks in advance! :D
Solution 1:[1]
I ended up solving it by modifying the functions a bit. Before, my run() function returned the output of the command. By returning the process proc, I could monitor the timeout for the proc.communicate(). This is the portion that waits until the process is done. If the process takes longer than timeout, then I ask if it is done, by looking into proc.returncode. If it is other than None it has finished. If it is None, then I recursively kill every child and finally the parent process itself.
async def run(cmd):
print("Running: ", cmd)
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
return proc
async def run_with_timeout(cmd, timeout=20):
proc = await run(cmd)
try:
output = await asyncio.wait_for(proc.communicate(), timeout=timeout)
stdout, _ = output
return str(stdout).strip()
except asyncio.TimeoutError:
if proc.returncode is None:
parent = psutil.Process(proc.pid)
for child in parent.children(recursive=True):
child.terminate()
parent.terminate()
print("Terminating Process '{0}' (timed out)".format(cmd))
asyncio.run(run_with_timeout(['stress', '--cpu', '2'], timeout=5))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Xavier Merino |
