Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kill a python subprocess that does not return

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

like image 208
Coding Newbie Avatar asked Dec 26 '21 19:12

Coding Newbie


1 Answers

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"}
like image 64
kosciej16 Avatar answered Oct 18 '22 06:10

kosciej16