lilydocs
HTTP APIWebSocketConsumer
Fundamentals3 min read

Application lifecycle

Learn the build, start, and close steps and which resources the application owns.

In Lily, preparing the application and running the server are separate steps. This separation catches startup errors early and lets shutdown follow resource ownership.

Build: prepare the application#

build().await loads configuration, builds the DI container, initializes Singleton services, and creates controllers and policies. Route and policy errors are rejected at this stage. A successful build has not opened a listener yet.

Inside the async startup function
let app = AppBuilder::new("127.0.0.1:8080")
    .tls_disabled()
    .build().await?;

// The application is ready. It is not accepting requests yet.
app.start().await?;

If startup fails, Lily cleans up the resources it created and owns. Controller, middleware, and guard constructors run during application setup; they are not called again for each request.

Start: run the server#

start().await opens the listener, starts managed background services, and watches platform signals. An App that has been started cannot be started a second time.

Use start_with_cancellation(token) when the enclosing application owns the shutdown signal. This CancellationToken is owned by the application; it is not the same interface as the read-only ExecutionCancellation received by a handler.

Close: wait for shutdown#

You can clone App to retain a separate shutdown handle. close().await stops new work and waits for the shutdown result of managed resources. An application that has not been started can also be closed:

Close after build without starting
let app = AppBuilder::new("127.0.0.1:8080")
    .tls_disabled()
    .build().await?;
app.close().await?;

Repeated or concurrent close calls share the first shutdown's deadline and result. Each call does not create a new shutdown budget.

Resource shutdown order#

  1. Admission of new work stops; ongoing managed work is terminated.
  2. Request resources and request scopes are cleaned up.
  3. DI services owned by the application are closed.
  4. Telemetry providers owned by the application are flushed and shut down.

These steps run in coordination. A timeout or abort call alone does not prove that the associated task has actually ended. Cancellation and shutdown explains these boundaries.

Ownership boundaries#

Resource Owner
Default App container Lily HTTP application
Request-scoped services The corresponding request scope
Worker registered with add_background_service HTTP host
Container supplied through .container(...) The external application that created it
External tracing runtime The application that configured it
The application's own tokio::spawn task Application code

At most one HTTP lifecycle can attach to the same container. Controllers, middleware, and guards do not have a separate async dispose hook. Keep resources that need cleanup in DI services implementing ServiceTrait.

Sources for this page v0.1.0

This guide is based on the reviewed framework commit.

QUICK START
Documentation
Source code