My app has API endpoints to import data from another system.
Data packges can be relatively large and take more then 30 seconds to process, which raises anvil.server.Timeout error.
I would like to catch this exception to send a response with a reasonbale code and message that suggests using special bulk API that uses background tasks to process large amount of data.
I tried signal to set my own timeout in the server function but it doesn’t work - it riases timeout immediately.
Is is possible at all?
Is it the upload/download of the data payload itself that takes longer than 30 seconds?
If not, I would see if I could move every single call to a background task. Your API endpoint could receive the data and immediately pass it to a background task.
If you need to communicate back through the API, you can take your data payload, and add it to a data table, along with the state of the processing, then launch the background task and allow the task to interface through the data table, updating its status and recording any results of work done. It could even provide a rowID or unique ID from the table to the call for processing as a response.
When the API is queried about the status of that work, it can be retrieved from the data table, or reply with some other status like “in progress”.
Thanks for suggestion. However, the question relates to the timeout itself.
Lets say, another app or service sends request to a regular API endpoint. It will receive a generic 500 response without any description in case of timeout if the load is to big.
Instead, I’d like to send my own response (408 for example) with a message that clearly describes what happened and advises to use a special bulk API endpoint.
How were you attempting to set your timer? Could you share a code snippet?
here the code I used to try and test this apporoach
import signal
def timeout_handler(signum, frame):
raise TimeoutError
def set_timeout(num_seconds):
print(f'Num seconds: {num_seconds}')
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(num_seconds)
@anvil.server.http_endpoint("/timeout", methods=["GET", "POST"])
def long_running_function():
try:
set_timeout(28)
for i in range(33):
print(f"i: {i}")
time.sleep(1)
return anvil.server.HttpResponse(
200,
json.dumps({'status': 'success'}),
{'content-type': 'application/json'},
)
finally:
signal.alarm(0)
return anvil.server.HttpResponse(
202,
json.dumps({'status': 'incomplete', 'message': 'Request timed out. Use bulk API to process this request.'}),
{'content-type': 'application/json'},
)
How does signal achieve this timeout counting? I’m not familiar with this library and is probably key to understanding why it times out immediately.
What about just doing something really basic along these lines first?
import time
import json
import anvil.server
@anvil.server.http_endpoint("/timeout", methods=["GET", "POST"])
def long_running_function():
start_time = time.time()
timeout_seconds = 28
for i in range(33):
# Check if the current time exceeds the start time + timeout_seconds
if time.time() > start_time + timeout_seconds:
print("Request timed out. Use bulk API to process this request.")
return anvil.server.HttpResponse(
202,
json.dumps({'status': 'incomplete', 'message': 'Request timed out. Use bulk API to process this request.'}),
{'content-type': 'application/json'},
)
print(f"i: {i}")
time.sleep(1) # Simulate work
# If the loop completes within the timeout, return success
return anvil.server.HttpResponse(
200,
json.dumps({'status': 'success'}),
{'content-type': 'application/json'},
)
I can’t see how, logically in any scenario that the code inside the function would ever be executed if the payload times out while the endpoint code is receiving data.
@anvil.server.http_endpoint()
is a decorator, it wraps the function it decorates like:
REGISTERED ENDPOINT CODE
your func()
END REGISTERED ENPOINT CODE
If the endpoint fails because the receiving payload is too big / takes too long, I would think nothing you write in the inner function could change the behavior or response of the outer function, especially since it will never be executed and instead aborts with that generic 500 response you want to change.
I am saying I think you need to look at changing the behavior of the endpoint decorator, but I don’t know if that is going to be easy.