TLDR I want to kill a subprocess like top while it is still running
I am using Fastapi to run a command on input. For example if I enter top my program runs the command but since it does not return, at the moment I have to use a time delay then kill/terminate it. However I want to be able to kill it while it is still running. However at the moment it won't run my kill command until the time runs out.
Here is the current code for running a process:
@app.put("/command/{command}")
async def run_command(command: str):
subprocess.run([command], shell=True, timeout=10)
return {"run command"}
and to kill it
@app.get("/stop")
async def stop():
proc.kill()
return{"Stop"}
I am new to fastapi so I would be grateful for any help
It's because subprocess.run
is blocking itself - you need to run shell command in background e.g. if you have asnycio loop already on, you could use subprocesses
import asyncio
process = None
@app.get("/command/{command}")
async def run_command(command: str):
global process
process = await asyncio.create_subprocess_exec(
command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
return {"run command"}
@app.get("/stop")
async def stop():
process.kill()
return {"Stop"}
Or with Popen
from subprocess import Popen
process = None
@app.get("/command/{command}")
async def run_command(command: str):
global process
process = Popen([command]) # something long running
return {"run command"}
Adding the timeout option can be tricky as you do not want to wait until it completes (where you could indeed use wait_for function) but rather kill process after specific time. As far I know the best option would be to schedule other process which is responsible for killing main one. The code with asyncio could look like that:
@app.get("/command/{command}")
async def run_command(command: str):
global process
process = await asyncio.create_subprocess_exec(
command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
loop = asyncio.get_event_loop()
# We schedule here stop coroutine execution that runs after TIMEOUT seconds
loop.call_later(TIMEOUT, asyncio.create_task, stop(process.pid))
return {"run command"}
@app.get("/stop")
async def stop(pid=None):
global process
# We need to make sure we won't kill different process
if process and (pid is None or process.pid == pid):
process.kill()
process = None
return {"Stop"}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With