# Graceful shutdown

> Finish in-flight work before the process exits

When a service is stopped, for example during a deploy or when `encore run` restarts it, it receives `SIGTERM` (or `SIGINT`, and Ctrl+C or Ctrl+Break on Windows). Encore.rs handles the signal for you: it stops taking new Pub/Sub messages, waits for in-flight message handlers, closes connections, flushes metrics and traces, and exits the process.

Your HTTP server is part of your app, not Encore, so Encore can't drain its requests on its own. The `encore::shutdown` module lets the two cooperate: your server stops accepting new requests and finishes the ones in flight, and Encore cleans up after that.

## Shutting down an axum server

Pass `encore::shutdown::signal()` to your server's graceful shutdown, then await `encore::shutdown::drained()` and exit with the code it returns:

```rust
-- src/main.rs --
#[tokio::main]
async fn main() {
    encore::initialize();

    let app = axum::Router::new().route("/", axum::routing::get(|| async { "Hello!" }));
    let addr = std::env::var("ENCORE_LISTEN_ADDR").unwrap_or_else(|_| {
        let port = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string());
        format!("0.0.0.0:{port}")
    });
    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();

    axum::serve(listener, app)
        .with_graceful_shutdown(encore::shutdown::signal())
        .await
        .expect("serve");

    std::process::exit(encore::shutdown::drained().await.code());
}
```

The sequence on a shutdown signal is:

1. `signal()` resolves, and axum stops accepting connections and waits for in-flight requests to finish.
2. At the same time, Encore stops Pub/Sub subscriptions from fetching new messages and waits for the handlers already running.
3. When `serve` returns, `drained()` tells Encore your app has finished its work.
4. Encore closes its Postgres, Redis, and object storage connections, exports a final round of metrics, and sends any buffered trace spans.
5. `drained()` returns a `ShutdownResult`, and your app exits with its `code()`.

Database and other infrastructure connections stay open until your app has drained, so requests that finish during shutdown can still use them.

## `signal()`

```rust
pub fn signal() -> impl Future<Output = ()> + Send + 'static
```

Returns a future that resolves once a shutdown signal is received. Calling it is what makes shutdown cooperative: from then on, Encore's cleanup waits for `drained()` (or the deadline) instead of starting immediately. Call it at startup, before the signal can arrive, as passing it to `with_graceful_shutdown` does.

The future can be awaited anywhere, for example to stop a background loop:

```rust
let stop = encore::shutdown::signal();
tokio::select! {
    _ = stop => {}
    _ = run_worker_loop() => {}
}
```

## `drained()`

```rust
pub async fn drained() -> ShutdownResult
```

Tells Encore your app has finished its work, then waits for Encore's cleanup to complete. If no signal has started a shutdown yet, `drained()` starts one, so you can also call it when your app decides to exit on its own.

It returns `encore::shutdown::ShutdownResult`:

| Variant | Meaning | `code()` |
|---|---|---|
| `Success` | Cleanup finished within the deadline. | `0` |
| `Timeout` | The deadline passed before cleanup finished. | `1` |
| `Failed` | Cleanup stopped unexpectedly. | `1` |

Exit with `result.code()` once `drained()` returns. Encore leaves exiting to your app in this case, so that code after `drained()` still runs.

## The deadline

Shutdown has a deadline: five seconds unless the environment configures another. If your app hasn't drained by then, Encore proceeds with cleanup anyway and the result is `Timeout`. Buffered traces and metrics are still flushed after a timeout, for up to five more seconds.

If your app called `signal()` but never calls `drained()`, Encore exits the process with code `1` once cleanup completes after the deadline. As a last resort, a process that is still running well after the deadline is forced to exit with code `1`.

## Apps that don't use `signal()`

If you never call `signal()`, shutdown isn't cooperative. On a signal Encore drains Pub/Sub, closes connections, flushes telemetry, and exits the process with the result's code right away, without waiting for your HTTP server. In-flight requests are cut off, and requests still running may find their database connections closed. Use `signal()` and `drained()` in any service that serves requests.

## Services that serve no requests

A service that only runs Pub/Sub subscriptions or other background work can call `encore::run_blocking()` from a synchronous `main`. It starts the registered subscriptions, blocks until a shutdown signal, drains, and exits the process:

```rust
fn main() {
    encore::initialize();
    encore::run_blocking();
}
```

`run_blocking()` blocks the calling thread on Encore's own runtime, so call it from a plain `fn main`, not from inside an async context such as `#[tokio::main]`. In an async `main`, await `signal()` and then `drained()` instead:

```rust
#[tokio::main]
async fn main() {
    encore::initialize();
    encore::shutdown::signal().await;
    std::process::exit(encore::shutdown::drained().await.code());
}
```

## Tests

Encore doesn't install its signal handling under `encore test`. A test that awaits `signal()` waits forever.
