lilydocs
HTTP APIWebSocketConsumer
Operations4 min read

Graceful shutdown

Observe admission closure, delivery drain, broker close and owned resource cleanup within one budget.

Consumer shutdown stops accepting new work, reconciles accepted work and scope cleanup, closes broker resources and terminates application resources it owns. A cancellation or abort request is not evidence that these steps have completed.

Choose one application budget#

TOML
[lifecycle]
shutdown_timeout_secs = 30

The default total allowance is 30 seconds. It starts at the first shutdown initiation; later polling of the supervisor does not renew it. Graceful and force phases share the same hard root deadline. The framework reserve is the smaller of one quarter of the total and two seconds; each stage does not receive another 30 seconds.

Align deployment termination time with the application’s resources and this budget. A delivery has its own reserve calculation, capped by the application root; see deadlines. Startup rollback also adopts the shared attempt budget; pending DI rollback does not promise an independent extra 30 seconds.

The shutdown sequence#

  1. New registration, intake and execution admission close.
  2. Accepted delivery pipelines, transaction/relay work, scope cleanup and settlement drain.
  3. Bounded cooperative force and actual task joins are observed if needed.
  4. Queue/channel/connection close evidence is collected.
  5. Owned DI and outstanding user/scope barriers, the signal monitor and owned telemetry complete.
  6. The immutable terminal result and any original shutdown report are published.

Consumer does not claim disposal ownership for an external container or external telemetry. The parent application must wait for all users, including Consumer, before closing shared resources. Tasks and resources created directly by the application also require cancellation and joins by their own owner.

Triggers and force behavior#

On the signal-based run() path, Unix SIGINT/SIGTERM request graceful shutdown; SIGQUIT or a subsequent signal triggers bounded force escalation. Token and managed paths do not install OS listeners. .shutdown().await requests graceful shutdown and observes the retained result; framework deadline coordination applies when work takes longer.

Force notification does not immediately remove settlement authority on a healthy channel; a delivery completing cooperatively may still be ACKed in the remaining time. When final abort is required, settlement authority is revoked and task termination is observed. An abort request is not a task join or evidence of remote ACK/COMMIT.

Read the report even after an error#

Rust
use lilyrs::consumer::{ConsumerError, ManagedConsumer};

async fn stop(consumer: &ManagedConsumer) -> Result<(), ConsumerError> {
    let result = consumer.shutdown().await;
    if let Some(report) = consumer.shutdown_report() {
        println!("completion={:?}, successful={}", report.completion, report.is_success());
        println!("joined={}, queue_closed={}",
            report.runtime_joined, report.queue_close_reconciled);
    }
    result
}
Completion Meaning
GracefulCompleted Owned barriers and runtime join completed successfully and gracefully
ForcedCompleted Successful reconciliation using required force
Failed Resources are terminal but at least one lifecycle operation failed
Incomplete Required termination or coordinator receipt evidence is missing

snapshot().shutdown.completed means the actual managed task joined; it does not mean every resource closed successfully. report.is_success() is true only for fully reconciled successful graceful/forced shutdown. The report is None before runtime join and may remain absent if failure occurred before coordinator evidence was produced. A missing report is not success.

Preserve original evidence and respect technical limits#

Resource report fields owned, started, terminal and succeeded are distinct: a disposer may be terminal even when it returned an error. Caller-owned or absent resources retain false fields. Later force completion does not erase earlier timeout/panic evidence, and late cleanup does not rewrite the immutable report.

Async deadlines cannot preempt synchronous code that never yields or a blocking destructor. An incomplete owner may continue holding resources; the framework does not present that as successful cleanup. Await the explicit result as in the managed example, instead of shutting down the runtime immediately after Drop.

Sources for this page v0.1.0

This guide is based on the reviewed framework commit.

QUICK START
Documentation
Source code