navree

Lifecycle hooks

Run code when the app boots and when it shuts down: on_init, on_bootstrap, on_shutdown, background tasks and graceful shutdown.

On this page

Introduction

Some providers need to do work at a particular moment: load data before the first request, start a background loop once everything is wired, flush a buffer or close a pool on the way out. Navree gives them three hooks, with the same meaning as in NestJS:

HookNestJSWhen it runs
on_initonModuleInitAt boot, once every singleton exists
on_bootstraponApplicationBootstrapAt boot, after every on_init succeeded
on_shutdownonApplicationShutdownDuring a graceful shutdown, after background tasks have stopped

The hooks belong to the Lifecycle trait, in navree::kernel. Each has a default that does nothing, so you implement only the ones you need.

Writing hooks

This provider loads exchange rates at boot (and stops the boot if it can't), then refreshes them every hour until the app shuts down:

src/modules/rates/mod.rs

A hook runs only once you register it: m.lifecycle::<T>() tells the kernel to run the hooks of provider T. Two rules apply:

  • T must be a provider of the same module. Registering hooks for a type the module doesn't provide fails the boot (and check) with a "cannot resolve" error that says it's required by a lifecycle registration of the module.
  • T must be a singleton. The kernel resolves lifecycle providers at boot, outside any request scope, so a request-scoped one fails the boot with a "resolved outside a request scope" error. check doesn't catch this one; only a real boot does.

Every hook receives the AppContext, a cheap handle to the booted app: use it to resolve other providers, to read the shutdown token and to spawn tracked tasks.

The order of the hooks

At boot

When the app boots, for serve, any other command, or TestApp:

  1. The graph is validated

    Every module's register runs once, and the kernel checks the graph: visibility, cycles, scopes, duplicate commands, lifecycle registrations. Nothing is constructed yet. See boot-time checks.

  2. Singletons are built

    Every singleton is constructed, module by module in dependency order (a module's imports before the module).

  3. on_init runs

    on_init runs for every registered provider, in module dependency order, one at a time.

  4. on_bootstrap runs

    Once every on_init succeeded, on_bootstrap runs for every registered provider, in the same order.

Then the command runs (for serve, the server starts listening). Within one module, hooks run in the order the module registered them.

If a hook returns an error, the boot stops with an error that names the module and the hook:

Text

A failed boot cancels the app's shutdown token, but it runs no on_shutdown hooks, not even for providers whose on_init already ran.

At shutdown

A graceful shutdown (App::shutdown) has three phases:

  1. The shutdown token is cancelled. serve stops accepting connections, workers stop taking new jobs, and your background loops see token.cancelled().
  2. Tracked tasks drain. The kernel waits for every task spawned on the app's tracker to finish.
  3. on_shutdown runs for every registered provider, in reverse module dependency order, so a module shuts down before the modules it depends on.

Each of phases 2 and 3 may take up to the shutdown timeout, 30 seconds by default. Change it on the app builder:

Rust

Every on_shutdown hook runs even if an earlier one fails; the failures are collected and returned together. A phase that runs out of time makes the shutdown return an error such as graceful shutdown timed out after 10s waiting for tracked tasks.

Background tasks

Long-running work started by a hook (a poller, a cache warmer, a metrics upkeep loop) should:

  • watch ctx.shutdown_token(), a CancellationToken, and stop when it's cancelled;
  • be spawned with ctx.tracker().spawn(..), so a graceful shutdown waits for it to finish.

A plain tokio::spawn isn't waited for: the shutdown hooks may then run while your task is still using the services they close.

What the framework does in its hooks

Navree's own modules use the same hooks, which shows what each is for:

ModuleHookWhat it does
HttpModuleon_bootstrapBuilds the router once, so a route without an access declaration, a duplicate route or a missing authenticator stops the boot before a port is bound
ApiModuleon_bootstrapBuilds the OpenAPI document, so a schema conflict stops the boot
ObserveModuleon_bootstrapStarts the Prometheus recorder's upkeep loop on the tracker
DatabaseModuleon_shutdownCloses the connection pool (SQLite then checkpoints its write-ahead log)
LoggerModuleon_shutdownFlushes the log file
EventsModuleon_shutdownDetaches the event bus, so no event is delivered once the app is shutting down

Graceful shutdown

AppBuilder::run(), which an app's main calls, starts a Tokio runtime, runs the command from the command line and listens for Ctrl-C (and SIGTERM on Unix). A signal cancels the shutdown token. For serve, that starts this sequence:

  1. The server stops accepting connections

    serve lets in-flight requests finish, for up to http.shutdown_grace_secs (30 seconds by default). Requests still running after that are dropped, with a warning in the log.

  2. Units of work complete

    With DatabaseModule, the rest of each request runs in a task on the app's tracker, so a request whose client went away still commits or rolls back.

  3. The app shuts down

    The kernel waits for tracked tasks, then runs the on_shutdown hooks, each phase within the shutdown timeout.

Every other command shuts down the same way when it returns: queue:work and schedule:work stop at the signal, finish what they're doing and return, and then the hooks run. Dropping an App without calling shutdown cancels its token but runs no hooks.

Testing hooks

Boot the app as the test would run it, then shut it down explicitly; dropping a TestApp doesn't run the shutdown hooks:

Rust

TestApp::shutdown().await does the same for a test app, and also deletes its temporary database.

Popular pages

  • Installation
  • Your first app
  • Routing and controllers
  • Validation
  • Authentication
  • Benchmarks