Request timeouts
Web endpoint (a.k.a webhook) requests should complete quickly, ideally within a
few seconds. All webhook Function types
(web_endpoint
, asgi_app
, wsgi_app
)
have a maximum HTTP request timeout of 150 seconds enforced. However, the
underlying Modal function can have a longer timeout.
In case the function takes more than 150s to complete, a HTTP status 302
redirect response is returned pointing at the original URL with an appended
__modal_function_call_id=fc-123abc
query parameter corresponding to the
triggered web endpoint. Most web browsers allow for up to 20 such redirects,
effectively allowing up to 50 min (20 * 150s) runtime for web endpoints before
the request times out. Some libraries and tools might require you to add a flag
or option in order to follow redirects automatically, e.g. curl -L ...
or
http --follow ...
.
The eventual landing URL can be reloaded without triggering a new request, and will instead fetch the result of the original call.
wait_for_response=False
In cases when you want a web endpoint to trigger and return the result URL
immediately, you can add the wait_for_response=False
flag to your
web_endpoint/asgi_app/wsgi_app decorator. The decorated function will then be
immediately triggered and a 202 accepted HTTP response will be returned, along
with a JSON payload containing the aforementioned result URL:
{"result_url": "...?__modal_function_call_id=..."
}. This can be useful if you
for example want to store the result URL somewhere for later access, or
immediately redirect a web browser to a URL that won’t trigger a new function
call on manual refresh.
Polling solutions
Sometimes it can be useful to be able to poll for results rather than wait for a
long running HTTP request. The easiest way to do this is to have your web
endpoint spawn a modal.Function
call and return the function call id that
another endpoint can use to poll the submitted function’s status. Here is an
example:
import fastapi
from modal import Stub, asgi_app
from modal.functions import FunctionCall
stub = Stub()
web_app = fastapi.FastAPI()
@stub.function()
@asgi_app()
def fastapi_app():
return web_app
@stub.function()
def slow_operation():
...
@web_app.post("/webhook")
async def accept_job(request: fastapi.Request):
call = slow_operation.spawn()
return {"call_id": call.object_id}
@web_app.get("/result/{call_id}")
async def poll_results(call_id: str):
function_call = FunctionCall.from_id(call_id)
try:
return function_call.get(timeout=0)
except TimeoutError:
http_accepted_code = 202
return fastapi.responses.JSONResponse({}, status_code=http_accepted_code)
Document OCR Web App is an example that uses this pattern.