I have a FastAPI app with an end point that may have to do a long computation. As a replacement of the actual code let me use this mock example, in which I am trying to keep the features that I think are relevant.
from fastapi import FastAPI
from fastapi.responses import JSONResponse
import subprocess
app = FastAPI()
@app.put("/expensive")
def expensive():
command = "for x in $(seq 1 10000); do echo ${x}; done"
subprocess.run(command, shell=True, check=True)
return JSONResponse(content=True)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"deleteme:app",
host='0.0.0.0',
port=12345,
reload=True
)
As in the mock example, the actual code run a third party software that does a computation that could, in some cases, take a long time.
When I send, with Postman, a PUT request, the app will start printing the natural numbers from 1
to 10000
. I can cancel the request in Postman. However, the computation in the app will continue.
Question: I would like the user to be able to have a mechanism to cancel a specific request made to http://0.0.0.0:12345/expensive
. What are ways to do this?
My lack of experience with Web APIs also causes that I am not finding the right key words to search about the subject. For example, combinations of fastapi request abort
, or web api request cancel
, don't seem to lead to posts discussing this topic.
A mechanism that I can think about could be:
request_id
.request_id:PID
.request_id
of the request that they would like to cancel.request_id
to then kill the subprocess with the corresponding PID.This seem convoluted.
Cancel Fetch request If you want to cancel a Fetch request, you need to use the AbortController API. You can use the constructor to create a new AbortController object. It has a read-only property AbortController.
Use the Request object directly For that you need to access the request directly. By declaring a path operation function parameter with the type being the Request FastAPI will know to pass the Request in that parameter. Note that in this case, we are declaring a path parameter beside the request parameter.
FastAPI (Async) - Python FastAPI in asynchronous mode clocks in at ~228 requests per second.
There's a way to "cancel"(or kill) the request(or subprocess). But it isn't easy.
First, Canceling the actual request is impossible because it is a FastAPI process. I don't know how exactly FastAPI runs internally, but it's a low-level story.
Second, killing the subprocess is possible. You can find the subprocess's PID and the only thing left to do is kill the process. It's not fancy, though.
There are lots of alternative solutions.
Set timeout
: subprocess.run()
has timeout
parameter. It will raise subprocess.TimeoutExpired
Use BackgroundTasks
: FastAPI(Starlette, actually) has nice feature, Background Tasks
Separate your service with a 3rd party: It's recommended if your process(or another app) takes a very long time. Celery is for doing this. Many best practices are there, and I think it is a proper way what you think.
In my experience, long running requests have always resulted in one problem or another. A few 'heavy' requests should not be allowed to prevent the workers from serving numerous small requests. I would suggest a polling based mechanism with an ETA feature.
Another option is to switch to websockets. With a pingpong we can detect the connection failure and stop the process when needed.
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