Cookbook / Production

Handler timeouts

Cap how long any route may take, override it per route, and answer 503 instead of hanging.

A slow database, a stuck vendor call or a runaway loop should not tie up a request forever. handler_timeout sets a default limit for every route; a handler that runs over is cancelled at its next .await and the client gets 503 Service Unavailable.

src/main.rs
use axeon::Server;
use std::time::Duration;

fn main() {
    let mut app = Server::new();

    // The default for every route. Off unless you set it.
    app.handler_timeout(Duration::from_secs(15));

    // Tighter for a hot read path...
    app.route("/v1/products")
        .timeout(Duration::from_secs(2))
        .get(|_req| async { "[]" });

    // ...looser for slow work such as large uploads...
    app.route("/documents")
        .stream_body()
        .timeout(Duration::from_secs(300))
        .post(|_req| async { "stored" });

    // ...and none at all for a long-running stream.
    app.route("/assistant").no_timeout().post(|_req| async { "streamed" });

    // Takes 5 seconds but is allowed only 1.
    app.route("/slow")
        .timeout(Duration::from_secs(1))
        .get(|_req| async {
            tokio::time::sleep(Duration::from_secs(5)).await;
            "too late"
        });

    app.listen("127.0.0.1:3000").expect("server failed to start");
}
Terminal
curl -i http://127.0.0.1:3000/slow    # 503 after one second

Notes#

  • The limit covers the route's middleware and handler together. Global middleware (logging, metrics, request IDs) runs outside it, so it still sees and records the 503.
  • A cancelled handler stops at its next .await and everything it holds is dropped, so an open database transaction rolls back. Work that must finish regardless belongs in a spawned task or a background job.
  • A streamed response only has to start in time. Once the first bytes are sent, the stream may run as long as it needs.
  • Timeouts are kept when you mount a router, and on_error sees ServerError::HandlerTimeout like any other error.
  • This differs from request_timeout, which bounds how long a slow *client* may take to send its request.