Files
launcher/docs/adr/ADR-002-reverse-order-shutdown.md
Rene Nochebuena f2e3faa1d6 feat(launcher): initial stable release v0.9.0
Application lifecycle manager enforcing a three-phase init/wire/start sequence with reverse-order graceful shutdown and per-component stop timeouts.

What's included:
- `Component` interface (OnInit / OnStart / OnStop) and `Hook` type for BeforeStart wiring functions
- `Launcher` interface with Append, BeforeStart, Run (blocks on SIGINT/SIGTERM), and idempotent Shutdown(ctx)
- `New(logger, opts...)` constructor with configurable ComponentStopTimeout (default 15 s); no global state

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
2026-03-18 23:49:12 +00:00

2.1 KiB

ADR-002: Reverse-Order Shutdown

Status: Accepted Date: 2026-03-18

Context

When stopping an application, components must be stopped in the reverse of the order they were started. A component that depends on another (e.g. an HTTP server that uses a database connection pool) must be stopped before the component it depends on. If the database pool were closed first, any in-flight request handled by the HTTP server would fail with a connection error rather than completing gracefully.

Decision

stopAll iterates l.components from the last index to the first:

for i := len(l.components) - 1; i >= 0; i-- {
    // stop l.components[i]
}

Components are typically registered in dependency order (dependencies first, then dependents). Reverse-order shutdown therefore stops dependents before dependencies.

Each component's OnStop runs in its own goroutine. A time.After ticker enforces the per-component ComponentStopTimeout (default 15 seconds). If a component does not return within the timeout, the launcher logs an error and moves on to the next component — it does not block the remainder of the shutdown sequence.

Shutdown(ctx context.Context) error provides a caller-side wait: it closes shutdownCh (idempotent via sync.Once) and then blocks until doneCh is closed (when Run returns) or until ctx is done. The caller's context controls how long the caller is willing to wait for the entire shutdown; ComponentStopTimeout controls how long each individual component gets.

Consequences

  • Shutdown order mirrors startup order in reverse, which is correct for any DAG of component dependencies without requiring an explicit dependency graph.
  • Each component's timeout is independent — a single slow component does not block others from stopping.
  • Shutdown is idempotent: calling it multiple times from multiple goroutines (e.g. an OS signal handler and a health-check endpoint) is safe.
  • If ComponentStopTimeout is exceeded, the component is abandoned — resources may not be fully released. This is the correct trade-off for a graceful shutdown with a deadline; the alternative (waiting indefinitely) is worse.