lilydocs
HTTP APIWebSocketConsumer
Pipeline3 min read

Deadlines and cancellation

Use the normal message's single time budget, cooperative cancellation, and independent cleanup authority correctly.

A message's normal pipeline uses one absolute deadline. Cancellation requests that processing stop; it does not alone prove that the future or a side effect in an external system has definitely finished.

Choose the duration#

Precedence
Action #[timeout(seconds = N)]
  → controller #[timeout(seconds = N)]
  → ServerConfig::message_timeout_secs

The normal timeout is 1..=300 seconds. Before middleware, all guards, extractors, the action, response preparation, and normal reverse middleware share this budget. Each phase does not receive a fresh full duration.

Observe the signal#

Action snippet
#[message("bounded")]
#[timeout(seconds = 2)]
async fn bounded(&self, stopping: ExecutionCancellation, deadline: MessageDeadline)
    -> Result<NoReply, WebSocketActionError>
{
    let work = tokio::time::sleep(std::time::Duration::from_millis(10));
    let _remaining = deadline.remaining();
    tokio::select! {
        biased;
        _ = stopping.cancelled() => Ok(NoReply),
        _ = work => Ok(NoReply),
    }
}

The complete controller file includes the imports. ExecutionCancellation is observe-only; it provides no .cancel(), raw token, child token, or .0 access. If an external operation requires its own token, build the explicit bridge in the cache/Mongo example.

Cooperative window#

A local timeout triggers the execution signal. The framework continues polling the normal pipeline for a cooperative window of at most 250 ms; the shutdown root bound can shorten it. A new cancellation reason does not restart the window.

If the pipeline returns an actual result during this window, success or the application error is preserved. Receiving a signal does not automatically turn the result into a timeout. Otherwise, the execution slot is stopped while cleanup and scope completion ownership continue. On a healthy connection, the next message can be processed after the required evidence is complete; the typical local error is keep-open MESSAGE_TIMEOUT.

Independent budgets#

Field Default Scope
message_timeout_secs 30 s Entire normal message pipeline
message_cleanup_timeout_secs 10 s Aggregate termination exits
connection_middleware_timeout_secs 10 s Handshake/identity/admit/opened invocations, aggregate closed
connection_lifecycle_timeout_secs 30 s Default connected/disconnected invocation duration
handshake_timeout_secs 10 s Upgrade boundary
write_timeout_millis 5000 ms Write/flush; shared terminal/Close drain cap during shutdown
outbound_admission_timeout_millis 5000 ms Waiting for queue capacity

Inbound waiting, scope disposal, and transport work are not added as new phases to the normal message deadline. Cleanup receives its own CleanupCancellation; do not pass normal execution's signal into cleanup.

Guarantee boundaries#

Normal shutdown stops new admission; it does not immediately cancel an already selected action merely because draining began. Execution-owned send authority is removed at the cooperative execution cutoff, not immediately on the cancellation signal.

Tokio cannot interrupt a synchronously blocking poll, codec, or destructor midway. A database commit or partial broadcast is not considered rolled back by cancellation. In particular, PostgreSQL's cancellation type differs from WebSocket's; the HTTP example's Some(cancellation) does not compile here.

Sources for this page v0.1.0

This guide is based on the reviewed framework commit.

QUICK START
Documentation
Source code