1403 lines
40 KiB
Markdown
1403 lines
40 KiB
Markdown
# Runtime Lifecycle Subsystem
|
|
|
|
> **Project:** NX9-Auth\
|
|
> **Release baseline:** v0.3.0\
|
|
> **Scope:** Application runtime, startup, serving, signals, workers,
|
|
> shutdown hooks, cancellation, metrics, and resource termination\
|
|
> **Primary implementation:** `src/runtime/`
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 1. Purpose
|
|
|
|
The NX9-Auth runtime lifecycle subsystem provides the orchestration
|
|
boundary between the CLI entrypoint and the long-running HTTP
|
|
application. It coordinates configuration, dependency assembly, database
|
|
initialization, Axum router construction, listener binding, background
|
|
workers, operating-system signals, graceful shutdown, prioritized
|
|
cleanup hooks, runtime metrics, and final resource release.
|
|
|
|
The subsystem is intentionally explicit. Runtime state is represented by
|
|
a monotonic state machine rather than inferred from scattered booleans
|
|
or task state. Shutdown is coordinated rather than ad hoc, and worker
|
|
termination precedes final resource cleanup.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 2. Design Goals
|
|
|
|
The lifecycle architecture is designed around these goals:
|
|
|
|
1. **Deterministic startup** --- dependencies are initialized in a
|
|
defined order before serving traffic.
|
|
2. **Deterministic shutdown** --- termination follows an explicit
|
|
sequence from draining through resource closure.
|
|
3. **Monotonic runtime state** --- normal lifecycle transitions move
|
|
forward only.
|
|
4. **Low synchronization overhead** --- lifecycle state uses atomic
|
|
operations rather than a mutex.
|
|
5. **Explicit ownership** --- the `Application` container owns or
|
|
coordinates the principal runtime components.
|
|
6. **Graceful HTTP termination** --- in-flight requests are given an
|
|
opportunity to complete.
|
|
7. **Bounded worker shutdown** --- background tasks are cancelled and
|
|
joined with timeout handling.
|
|
8. **Ordered cleanup** --- shutdown hooks execute by defined priority.
|
|
9. **Signal-aware operation** --- normal termination signals initiate
|
|
graceful shutdown; escalation can force termination.
|
|
10. **Testability** --- state transitions, worker handling, builder
|
|
behavior, and hook ordering are independently testable.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 3. Architectural Principles
|
|
|
|
The runtime follows several engineering principles.
|
|
|
|
### 3.1 Explicit lifecycle over implicit lifecycle
|
|
|
|
Startup and shutdown are represented by named states. This makes
|
|
operational behavior observable and prevents lifecycle logic from being
|
|
distributed invisibly across unrelated modules.
|
|
|
|
### 3.2 Construction before exposure
|
|
|
|
The HTTP server is not considered running until configuration, database
|
|
initialization, migrations, application state, and router assembly have
|
|
completed.
|
|
|
|
### 3.3 Cancellation before destruction
|
|
|
|
Background work is asked to terminate before resources it may depend on
|
|
are released.
|
|
|
|
### 3.4 Ordered cleanup
|
|
|
|
Cleanup operations with dependencies can be expressed through
|
|
shutdown-hook priority instead of relying on incidental registration
|
|
order.
|
|
|
|
### 3.5 Idempotent shutdown initiation
|
|
|
|
Multiple shutdown triggers must not cause multiple independent teardown
|
|
sequences.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 4. High-Level Architecture
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
CLI["CLI entrypoint"] --> CFG["Load configuration"]
|
|
CFG --> BUILDER["ApplicationBuilder"]
|
|
BUILDER --> DB["Initialize database"]
|
|
DB --> MIG["Run migrations"]
|
|
MIG --> STATE["Build AppState"]
|
|
STATE --> ROUTER["Build Axum router"]
|
|
ROUTER --> APP["Application"]
|
|
APP --> LISTENER["Bind TCP listener"]
|
|
LISTENER --> SERVER["Axum HTTP server"]
|
|
APP --> WORKERS["WorkerManager"]
|
|
APP --> SIGNALS["SignalManager"]
|
|
APP --> CANCEL["ShutdownCoordinator"]
|
|
APP --> HOOKS["HookRegistry"]
|
|
APP --> METRICS["RuntimeMetrics"]
|
|
```
|
|
|
|
This diagram deliberately uses simple Mermaid labels so that it renders
|
|
consistently on GitHub.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 5. Runtime Component Map
|
|
|
|
-------------------------------------------------------------------------------
|
|
Component Primary location Responsibility
|
|
----------------------- ------------------------------- -----------------------
|
|
`Application` `src/runtime/application.rs` Central lifecycle
|
|
container and
|
|
orchestration
|
|
|
|
`ApplicationBuilder` `src/runtime/builder.rs` Dependency assembly and
|
|
application
|
|
initialization
|
|
|
|
`AtomicRuntimeState` `src/runtime/state.rs` Lock-free lifecycle
|
|
state tracking
|
|
|
|
`SignalManager` `src/runtime/signals.rs` SIGINT/SIGTERM handling
|
|
and escalation
|
|
|
|
`ShutdownCoordinator` `src/runtime/cancellation.rs` Cancellation
|
|
propagation
|
|
|
|
`WorkerManager` `src/runtime/workers.rs` Background task
|
|
grouping, tracking, and
|
|
termination
|
|
|
|
`HookRegistry` `src/runtime/hooks.rs` Prioritized
|
|
asynchronous shutdown
|
|
hooks
|
|
|
|
`RuntimeMetrics` `src/runtime/metrics.rs` Runtime-level
|
|
operational counters
|
|
|
|
`Lifecycle` `src/runtime/lifecycle.rs` Lifecycle abstraction
|
|
implemented by the
|
|
application
|
|
-------------------------------------------------------------------------------
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 6. Ownership Model
|
|
|
|
`Application` acts as the lifecycle ownership boundary.
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
APP["Application"]
|
|
APP --> CONFIG["Config"]
|
|
APP --> PROVIDER["Repository provider"]
|
|
APP --> POOL["Database pool handle"]
|
|
APP --> ROUTER["Axum router"]
|
|
APP --> STATE["AtomicRuntimeState"]
|
|
APP --> WORKERS["WorkerManager"]
|
|
APP --> SIGNALS["SignalManager"]
|
|
APP --> CANCEL["ShutdownCoordinator"]
|
|
APP --> HOOKS["HookRegistry"]
|
|
APP --> METRICS["RuntimeMetrics"]
|
|
```
|
|
|
|
The model avoids global mutable lifecycle state. Shared runtime
|
|
coordination is performed through purpose-specific primitives such as
|
|
atomics, cancellation tokens, task sets, and shared application state.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 7. Dependency Assembly
|
|
|
|
The builder separates construction from execution.
|
|
|
|
``` mermaid
|
|
sequenceDiagram
|
|
participant Main
|
|
participant Builder
|
|
participant Database
|
|
participant State
|
|
participant Router
|
|
participant App
|
|
|
|
Main->>Builder: build
|
|
Builder->>Database: initialize provider
|
|
Database-->>Builder: provider and pool
|
|
Builder->>Database: run migrations
|
|
Builder->>State: construct AppState
|
|
State-->>Builder: application state
|
|
Builder->>Router: build router
|
|
Router-->>Builder: router
|
|
Builder->>App: assemble runtime
|
|
App-->>Main: initialized application
|
|
```
|
|
|
|
This boundary is important because the runtime can fail during
|
|
construction without exposing a partially serving HTTP process.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 8. Lifecycle State Model
|
|
|
|
NX9-Auth defines the following lifecycle states.
|
|
|
|
------------------------------------------------------------------------
|
|
State Value Meaning
|
|
--------------------- ---------------------------- ---------------------
|
|
`Initializing` 0 Configuration and
|
|
initial runtime
|
|
construction
|
|
|
|
`Starting` 1 Database, migrations,
|
|
state, router, and
|
|
server preparation
|
|
|
|
`Running` 2 HTTP listener is
|
|
active and the
|
|
service is
|
|
operational
|
|
|
|
`Draining` 3 Shutdown has started
|
|
and HTTP traffic is
|
|
being drained
|
|
|
|
`StoppingWorkers` 4 Background workers
|
|
are being cancelled
|
|
and joined
|
|
|
|
`ExecutingHooks` 5 Registered shutdown
|
|
hooks are executing
|
|
|
|
`ClosingResources` 6 Long-lived resources
|
|
are being released
|
|
|
|
`Stopped` 7 Lifecycle termination
|
|
is complete
|
|
------------------------------------------------------------------------
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 9. Deterministic State Machine
|
|
|
|
``` mermaid
|
|
stateDiagram-v2
|
|
[*] --> Initializing
|
|
Initializing --> Starting
|
|
Starting --> Running
|
|
Running --> Draining
|
|
Draining --> StoppingWorkers
|
|
StoppingWorkers --> ExecutingHooks
|
|
ExecutingHooks --> ClosingResources
|
|
ClosingResources --> Stopped
|
|
Stopped --> [*]
|
|
```
|
|
|
|
Normal transitions are strictly forward. A runtime state must not
|
|
regress from a later lifecycle phase to an earlier phase.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 10. Runtime State Invariants
|
|
|
|
The lifecycle design is intended to preserve these invariants:
|
|
|
|
- `Running` cannot be reached before startup initialization succeeds.
|
|
- Normal state transitions are monotonic.
|
|
- Shutdown initiation is guarded against duplicate execution.
|
|
- Worker termination occurs before final resource closure.
|
|
- Shutdown hooks execute after worker shutdown begins and before final
|
|
resource release.
|
|
- `Stopped` represents completion of the lifecycle sequence.
|
|
- Emergency state advancement is explicit rather than silently
|
|
violating transition rules.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 11. Atomic Runtime State
|
|
|
|
`AtomicRuntimeState` stores lifecycle state using `AtomicU8`.
|
|
|
|
The compact representation is suitable because the state space is small
|
|
and fixed. Atomic operations allow state inspection and transitions
|
|
without introducing a mutex solely for lifecycle coordination.
|
|
|
|
The implementation uses atomic memory ordering, including `Acquire` and
|
|
`AcqRel`, to coordinate visibility of lifecycle changes between
|
|
asynchronous execution contexts.
|
|
|
|
### Why an atomic state machine?
|
|
|
|
- State reads are inexpensive.
|
|
- Transition attempts can use compare-and-exchange semantics.
|
|
- Duplicate transition attempts can be detected.
|
|
- Shutdown initiation can be guarded without a global lock.
|
|
- The state machine remains independent of Tokio scheduling.
|
|
|
|
`force_set()` exists for exceptional or escalation paths and should not
|
|
replace validated normal transitions.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 12. Startup Lifecycle
|
|
|
|
The normal startup path is:
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
A["Process starts"] --> B["Parse CLI"]
|
|
B --> C["Load configuration"]
|
|
C --> D["Create ApplicationBuilder"]
|
|
D --> E["Initialize database provider"]
|
|
E --> F["Run migrations"]
|
|
F --> G["Construct AppState"]
|
|
G --> H["Construct Axum router"]
|
|
H --> I["Transition to Starting"]
|
|
I --> J["Bind TCP listener"]
|
|
J --> K["Transition to Running"]
|
|
K --> L["Serve HTTP"]
|
|
```
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 13. Startup Sequence in Detail
|
|
|
|
### Phase 1 --- Entrypoint
|
|
|
|
The binary entrypoint parses CLI arguments. The `serve` command enters
|
|
the long-running server path.
|
|
|
|
### Phase 2 --- Configuration
|
|
|
|
Configuration is discovered and loaded before runtime dependencies are
|
|
assembled.
|
|
|
|
A configuration failure prevents startup.
|
|
|
|
### Phase 3 --- Application construction
|
|
|
|
`Application::builder(config).build().await` creates the runtime
|
|
container and initializes dependencies.
|
|
|
|
### Phase 4 --- Database initialization
|
|
|
|
The configured SQLite or PostgreSQL backend is initialized through the
|
|
database provider abstraction.
|
|
|
|
Pending migrations are applied before the service begins serving
|
|
requests.
|
|
|
|
### Phase 5 --- Application state
|
|
|
|
Database and repository dependencies are incorporated into `AppState`.
|
|
|
|
### Phase 6 --- Router construction
|
|
|
|
The Axum API and SPA routes are assembled using the initialized
|
|
application state.
|
|
|
|
### Phase 7 --- Listener binding
|
|
|
|
The runtime binds a `TcpListener` to the configured host and port.
|
|
|
|
Listener binding is a startup boundary: a bind failure must abort
|
|
startup rather than incorrectly reporting `Running`.
|
|
|
|
### Phase 8 --- Serving
|
|
|
|
After successful binding, the runtime transitions to `Running` and
|
|
executes `axum::serve` with graceful shutdown integration.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 14. Startup Failure Boundaries
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
START["Startup"] --> CFG{"Configuration valid"}
|
|
CFG -- No --> FAIL["Return startup error"]
|
|
CFG -- Yes --> DB{"Database initialized"}
|
|
DB -- No --> FAIL
|
|
DB -- Yes --> MIG{"Migrations successful"}
|
|
MIG -- No --> FAIL
|
|
MIG -- Yes --> ROUTER{"Router constructed"}
|
|
ROUTER -- No --> FAIL
|
|
ROUTER -- Yes --> BIND{"Listener bound"}
|
|
BIND -- No --> FAIL
|
|
BIND -- Yes --> RUN["Running"]
|
|
```
|
|
|
|
A failed prerequisite must not leave the application falsely marked as
|
|
operational.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 15. HTTP Server Lifecycle
|
|
|
|
The HTTP server is created only after dependency assembly.
|
|
|
|
``` mermaid
|
|
flowchart LR
|
|
LISTENER["TcpListener"] --> AXUM["axum serve"]
|
|
ROUTER["Router"] --> AXUM
|
|
SHUTDOWN["Graceful shutdown future"] --> AXUM
|
|
AXUM --> REQUESTS["HTTP requests"]
|
|
```
|
|
|
|
The graceful shutdown future connects external termination events to
|
|
Axum's server-draining behavior.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 16. Shutdown Architecture
|
|
|
|
Shutdown is not a single cancellation call. It is a sequence of
|
|
lifecycle phases.
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
SIGNAL["Shutdown trigger"] --> GUARD["Initiate shutdown once"]
|
|
GUARD --> CANCEL["Cancel root token"]
|
|
CANCEL --> DRAIN["Drain HTTP server"]
|
|
DRAIN --> STOP["Stop workers"]
|
|
STOP --> HOOKS["Execute shutdown hooks"]
|
|
HOOKS --> CLOSE["Close resources"]
|
|
CLOSE --> DONE["Stopped"]
|
|
```
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 17. Shutdown Triggers
|
|
|
|
Graceful shutdown may originate from:
|
|
|
|
- `SIGINT`
|
|
- `SIGTERM`
|
|
- programmatic cancellation through the shutdown coordinator
|
|
- another controlled runtime termination path
|
|
|
|
The first shutdown event initiates graceful termination.
|
|
|
|
A subsequent signal can be treated as escalation when graceful shutdown
|
|
is no longer appropriate.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 18. Signal Management
|
|
|
|
`SignalManager` is responsible for translating operating-system
|
|
termination signals into runtime shutdown events.
|
|
|
|
``` mermaid
|
|
sequenceDiagram
|
|
participant OS
|
|
participant Signals
|
|
participant Runtime
|
|
participant Server
|
|
|
|
OS->>Signals: first termination signal
|
|
Signals->>Runtime: initiate graceful shutdown
|
|
Runtime->>Server: begin draining
|
|
OS->>Signals: second termination signal
|
|
Signals->>Runtime: escalate termination
|
|
```
|
|
|
|
The distinction between first-signal graceful termination and subsequent
|
|
escalation prevents a stuck cleanup operation from indefinitely blocking
|
|
process termination.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 19. Cancellation Architecture
|
|
|
|
The shutdown coordinator uses `tokio_util::sync::CancellationToken`.
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
ROOT["Root cancellation token"]
|
|
ROOT --> HTTP["HTTP shutdown"]
|
|
ROOT --> WG1["Worker group A"]
|
|
ROOT --> WG2["Worker group B"]
|
|
ROOT --> AUX["Auxiliary subsystem"]
|
|
```
|
|
|
|
Child cancellation tokens provide hierarchical propagation while
|
|
allowing subsystems to respond independently.
|
|
|
|
Cancellation is a request to stop. It does not by itself prove that a
|
|
task has terminated; worker joining remains necessary.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 20. Worker Management
|
|
|
|
`WorkerManager` groups asynchronous tasks and tracks their lifecycle.
|
|
|
|
The implementation uses `tokio::task::JoinSet` for task coordination.
|
|
|
|
Responsibilities include:
|
|
|
|
- registering background work,
|
|
- grouping related tasks,
|
|
- propagating cancellation,
|
|
- waiting for worker completion,
|
|
- enforcing bounded shutdown,
|
|
- aborting tasks that exceed the permitted shutdown window.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 21. Worker Shutdown
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
A["StoppingWorkers"] --> B["Signal cancellation"]
|
|
B --> C["Wait for worker completion"]
|
|
C --> D{"Completed before timeout"}
|
|
D -- Yes --> E["Worker group stopped"]
|
|
D -- No --> F["Abort remaining tasks"]
|
|
F --> E
|
|
E --> G["ExecutingHooks"]
|
|
```
|
|
|
|
Bounded shutdown is essential. Graceful termination must not become an
|
|
unlimited wait on a non-responsive task.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 22. Worker Hierarchy
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
WM["WorkerManager"]
|
|
WM --> G1["TaskGroup A"]
|
|
WM --> G2["TaskGroup B"]
|
|
G1 --> T1["Task 1"]
|
|
G1 --> T2["Task 2"]
|
|
G2 --> T3["Task 3"]
|
|
```
|
|
|
|
Task grouping allows cancellation and joining to be reasoned about at
|
|
subsystem boundaries rather than as unrelated spawned tasks.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 23. Shutdown Hooks
|
|
|
|
The runtime supports asynchronous shutdown hooks through the
|
|
`ShutdownHook` abstraction.
|
|
|
|
``` rust
|
|
#[async_trait::async_trait]
|
|
pub trait ShutdownHook: Send + Sync {
|
|
fn name(&self) -> &'static str;
|
|
|
|
fn priority(&self) -> ShutdownPriority {
|
|
ShutdownPriority::Normal
|
|
}
|
|
|
|
async fn shutdown(&self) -> Result<()>;
|
|
}
|
|
```
|
|
|
|
Hooks are intended for cleanup operations that must participate in
|
|
lifecycle ordering.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 24. Hook Priorities
|
|
|
|
The defined order is:
|
|
|
|
1. `First`
|
|
2. `Normal`
|
|
3. `Last`
|
|
|
|
``` mermaid
|
|
flowchart LR
|
|
FIRST["First"] --> NORMAL["Normal"]
|
|
NORMAL --> LAST["Last"]
|
|
```
|
|
|
|
Priority is semantic. A cleanup dependency should be represented by the
|
|
appropriate priority rather than relying on accidental registration
|
|
order.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 25. Hook Execution Model
|
|
|
|
``` mermaid
|
|
sequenceDiagram
|
|
participant Runtime
|
|
participant Registry
|
|
participant First
|
|
participant Normal
|
|
participant Last
|
|
|
|
Runtime->>Registry: execute hooks
|
|
Registry->>First: shutdown
|
|
First-->>Registry: complete
|
|
Registry->>Normal: shutdown
|
|
Normal-->>Registry: complete
|
|
Registry->>Last: shutdown
|
|
Last-->>Registry: complete
|
|
Registry-->>Runtime: hook phase complete
|
|
```
|
|
|
|
A robust shutdown implementation should preserve cleanup progression
|
|
even when an individual hook reports an error, while ensuring failures
|
|
remain observable.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 26. Resource Closure
|
|
|
|
The `ClosingResources` phase follows worker shutdown and hook execution.
|
|
|
|
Resources may include:
|
|
|
|
- database connection pools,
|
|
- repository/provider state,
|
|
- runtime-owned communication primitives,
|
|
- logging or telemetry buffers,
|
|
- other long-lived infrastructure owned by the application.
|
|
|
|
The ordering prevents dependent background work from using resources
|
|
after they have been destroyed.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 27. Complete Shutdown Sequence
|
|
|
|
``` mermaid
|
|
sequenceDiagram
|
|
participant Signal
|
|
participant State
|
|
participant Server
|
|
participant Workers
|
|
participant Hooks
|
|
participant Resources
|
|
|
|
Signal->>State: initiate shutdown
|
|
State->>State: Running to Draining
|
|
State->>Server: graceful shutdown
|
|
Server-->>State: HTTP drained
|
|
State->>State: Draining to StoppingWorkers
|
|
State->>Workers: cancel and join
|
|
Workers-->>State: workers stopped
|
|
State->>State: StoppingWorkers to ExecutingHooks
|
|
State->>Hooks: execute by priority
|
|
Hooks-->>State: hooks complete
|
|
State->>State: ExecutingHooks to ClosingResources
|
|
State->>Resources: close
|
|
Resources-->>State: released
|
|
State->>State: ClosingResources to Stopped
|
|
```
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 28. Shutdown Failure Strategy
|
|
|
|
Shutdown differs from startup failure handling.
|
|
|
|
During startup, a failed mandatory prerequisite normally aborts startup.
|
|
|
|
During shutdown, a cleanup failure should generally be recorded while
|
|
allowing later cleanup phases to proceed where safe. Otherwise, one
|
|
failed hook could prevent unrelated resources from being released.
|
|
|
|
Important failure categories include:
|
|
|
|
- worker timeout,
|
|
- worker panic,
|
|
- shutdown hook error,
|
|
- resource-close error,
|
|
- signal escalation,
|
|
- cancellation races.
|
|
|
|
The exact error policy should remain explicit in implementation and
|
|
tests.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 29. Concurrency Model
|
|
|
|
The lifecycle subsystem is built on Tokio asynchronous execution.
|
|
|
|
Primary concurrency mechanisms include:
|
|
|
|
- Tokio tasks,
|
|
- `JoinSet`,
|
|
- `CancellationToken`,
|
|
- atomic lifecycle state,
|
|
- shared application state,
|
|
- asynchronous shutdown hooks.
|
|
|
|
The lifecycle state machine itself does not depend on a blocking mutex.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 30. Thread-Safety Model
|
|
|
|
Runtime coordination should avoid mutable global state.
|
|
|
|
Cross-task lifecycle communication is expressed through primitives
|
|
designed for concurrent access:
|
|
|
|
``` mermaid
|
|
flowchart LR
|
|
TASK1["Task A"] --> ATOMIC["Atomic state"]
|
|
TASK2["Task B"] --> ATOMIC
|
|
SIGNAL["Signal task"] --> TOKEN["Cancellation token"]
|
|
TOKEN --> TASK1
|
|
TOKEN --> TASK2
|
|
```
|
|
|
|
Atomic state answers the question "which lifecycle phase are we in?"
|
|
|
|
Cancellation answers the question "should this work stop?"
|
|
|
|
Task joining answers the question "has this work actually stopped?"
|
|
|
|
These are separate concerns and should remain separate.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 31. Memory Ordering
|
|
|
|
The state implementation uses atomic memory ordering such as `Acquire`
|
|
and `AcqRel`.
|
|
|
|
At a conceptual level:
|
|
|
|
- **Acquire** prevents subsequent operations from being reordered
|
|
before an observed synchronization point.
|
|
- **Release** publishes prior operations before a synchronization
|
|
point.
|
|
- **AcqRel** combines both properties for read-modify-write
|
|
operations.
|
|
|
|
The runtime should use the weakest ordering that still preserves its
|
|
required synchronization semantics, but lifecycle correctness takes
|
|
priority over micro-optimization.
|
|
|
|
Changes to atomic ordering require careful review because a state
|
|
transition is part of inter-task coordination rather than merely a
|
|
numeric assignment.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 32. Why `AtomicU8`
|
|
|
|
The lifecycle has a small finite state space, making an integer-backed
|
|
atomic representation appropriate.
|
|
|
|
Benefits include:
|
|
|
|
- compact storage,
|
|
- constant-time reads,
|
|
- compare-and-exchange transitions,
|
|
- no lifecycle mutex contention,
|
|
- explicit conversion between stored values and semantic states.
|
|
|
|
The numeric representation is an implementation detail. Callers should
|
|
reason in terms of `RuntimeState`, not raw integers.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 33. Observability
|
|
|
|
Runtime transitions should be visible in structured logs.
|
|
|
|
A typical startup trace includes:
|
|
|
|
``` text
|
|
Initializing
|
|
Starting
|
|
Running
|
|
Listening on 0.0.0.0:8655
|
|
```
|
|
|
|
Structured transition logging should identify both the previous and next
|
|
state.
|
|
|
|
This allows operators to distinguish:
|
|
|
|
- configuration failure,
|
|
- database startup failure,
|
|
- listener failure,
|
|
- successful service readiness,
|
|
- graceful shutdown,
|
|
- worker shutdown delays,
|
|
- final termination.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 34. Runtime Metrics
|
|
|
|
`RuntimeMetrics` provides a lifecycle-level location for operational
|
|
measurements such as:
|
|
|
|
- request counts,
|
|
- error counts,
|
|
- active worker counts.
|
|
|
|
Metrics should describe runtime behavior without becoming an alternate
|
|
source of lifecycle truth. `AtomicRuntimeState` remains authoritative
|
|
for lifecycle phase.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 35. Lifecycle and Request Processing
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
RUN["Running"] --> LISTENER["TCP listener"]
|
|
LISTENER --> ROUTER["Axum router"]
|
|
ROUTER --> MW["Middleware"]
|
|
MW --> HANDLER["API or UI handler"]
|
|
HANDLER --> RESPONSE["HTTP response"]
|
|
SIGNAL["Shutdown signal"] --> DRAIN["Draining"]
|
|
DRAIN --> LISTENER
|
|
```
|
|
|
|
During normal operation, requests flow through the listener and router.
|
|
|
|
When graceful shutdown begins, the server stops accepting new work
|
|
according to Axum's graceful shutdown behavior and allows in-flight
|
|
requests to complete before later teardown phases proceed.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 36. Database Lifecycle
|
|
|
|
Database initialization is part of startup, not lazy runtime discovery.
|
|
|
|
``` mermaid
|
|
flowchart LR
|
|
CONFIG["Database configuration"] --> INIT["Initialize provider"]
|
|
INIT --> MIG["Run migrations"]
|
|
MIG --> POOL["Pool ready"]
|
|
POOL --> STATE["AppState"]
|
|
STATE --> RUN["Running"]
|
|
```
|
|
|
|
On shutdown, database resources are retained until worker and hook
|
|
phases that may depend on them have completed.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 37. Router Lifecycle
|
|
|
|
The router is assembled after its required state is available.
|
|
|
|
This provides a clean dependency direction:
|
|
|
|
``` text
|
|
configuration
|
|
-> database/provider
|
|
-> AppState
|
|
-> router
|
|
-> listener/server
|
|
```
|
|
|
|
The HTTP layer therefore consumes initialized domain infrastructure
|
|
instead of being responsible for constructing it.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 38. ApplicationBuilder Responsibilities
|
|
|
|
`ApplicationBuilder` should remain focused on dependency assembly.
|
|
|
|
Its responsibilities include:
|
|
|
|
- receiving validated runtime configuration,
|
|
- initializing database infrastructure,
|
|
- assembling provider/repository dependencies,
|
|
- constructing `AppState`,
|
|
- constructing the Axum router,
|
|
- returning an initialized `Application`.
|
|
|
|
It should not become a second application runtime.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 39. Application Responsibilities
|
|
|
|
`Application` owns the execution lifecycle after construction.
|
|
|
|
Its responsibilities include:
|
|
|
|
- state transitions,
|
|
- listener binding,
|
|
- HTTP serving,
|
|
- graceful shutdown integration,
|
|
- worker coordination,
|
|
- shutdown-hook execution,
|
|
- resource closure,
|
|
- lifecycle metrics and observability.
|
|
|
|
This division keeps construction and execution conceptually distinct.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 40. Lifecycle Trait
|
|
|
|
The lifecycle abstraction exposes the major phases through operations
|
|
such as:
|
|
|
|
``` text
|
|
initialize
|
|
start
|
|
shutdown
|
|
```
|
|
|
|
The abstraction makes lifecycle behavior testable independently from the
|
|
CLI command parser.
|
|
|
|
The CLI should initiate lifecycle operations rather than duplicate their
|
|
implementation.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 41. Security Considerations
|
|
|
|
Lifecycle correctness contributes directly to security.
|
|
|
|
### 41.1 Controlled startup
|
|
|
|
The service must not report itself as operational before mandatory
|
|
security-sensitive dependencies are ready.
|
|
|
|
### 41.2 Controlled shutdown
|
|
|
|
Sessions, audit operations, database work, and background tasks should
|
|
not be abandoned arbitrarily during ordinary termination.
|
|
|
|
### 41.3 No duplicate teardown
|
|
|
|
Duplicate shutdown sequences can cause double-close behavior,
|
|
inconsistent logging, or races between cleanup operations.
|
|
|
|
### 41.4 Bounded termination
|
|
|
|
An attacker or malformed task should not be able to prevent process
|
|
termination indefinitely by blocking graceful worker shutdown.
|
|
|
|
### 41.5 Signal escalation
|
|
|
|
Forced termination provides an operational escape path when graceful
|
|
shutdown cannot complete.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 42. Performance Characteristics
|
|
|
|
The lifecycle subsystem is not expected to dominate request-path
|
|
performance.
|
|
|
|
Approximate structural complexity:
|
|
|
|
Operation Characteristic
|
|
----------------------------- ----------------------------------------
|
|
Runtime state read O(1)
|
|
Atomic transition O(1)
|
|
Cancellation propagation proportional to cancellation tree
|
|
Worker shutdown O(n) relative to tracked tasks
|
|
Hook execution O(n) relative to registered hooks
|
|
Startup dependency assembly proportional to initialized components
|
|
|
|
Database initialization and migration cost dominate lifecycle startup
|
|
more than the state machine itself.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 43. Graceful Shutdown Timing
|
|
|
|
Shutdown timing should be treated as bounded operational policy.
|
|
|
|
``` mermaid
|
|
flowchart LR
|
|
T0["Shutdown requested"] --> T1["HTTP drain"]
|
|
T1 --> T2["Worker timeout window"]
|
|
T2 --> T3["Hooks"]
|
|
T3 --> T4["Resource close"]
|
|
T4 --> T5["Exit"]
|
|
```
|
|
|
|
Timeout values should be configuration-driven where operational
|
|
requirements justify it.
|
|
|
|
A timeout is not an error by itself; it is a boundary after which the
|
|
runtime may need to escalate from graceful waiting to forced task
|
|
abortion.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 44. Extension Model
|
|
|
|
New runtime subsystems should integrate through existing lifecycle
|
|
primitives rather than creating independent shutdown mechanisms.
|
|
|
|
A new subsystem should determine:
|
|
|
|
1. Who owns it?
|
|
2. When is it initialized?
|
|
3. Does it spawn background work?
|
|
4. Which cancellation token does it observe?
|
|
5. Must its tasks be joined?
|
|
6. Does it require a shutdown hook?
|
|
7. Which hook priority is correct?
|
|
8. Which resources must remain alive until it stops?
|
|
9. Which metrics and logs expose its lifecycle?
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 45. Example Custom Shutdown Hook
|
|
|
|
``` rust
|
|
use nx9_auth::runtime::{ShutdownHook, ShutdownPriority};
|
|
|
|
struct CustomCleanupHook;
|
|
|
|
#[async_trait::async_trait]
|
|
impl ShutdownHook for CustomCleanupHook {
|
|
fn name(&self) -> &'static str {
|
|
"custom_cleanup"
|
|
}
|
|
|
|
fn priority(&self) -> ShutdownPriority {
|
|
ShutdownPriority::First
|
|
}
|
|
|
|
async fn shutdown(&self) -> anyhow::Result<()> {
|
|
println!("Flushing custom buffer before shutdown...");
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
Registration belongs in application assembly where lifecycle ownership
|
|
is visible.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 46. Example Runtime Bootstrap
|
|
|
|
``` rust
|
|
use nx9_auth::config::Config;
|
|
use nx9_auth::runtime::{Application, Lifecycle};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let config = Config::find_and_load()?;
|
|
let mut app = Application::builder(config).build().await?;
|
|
app.start().await?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
The actual binary may perform additional CLI handling, logging
|
|
initialization, or command dispatch around this lifecycle.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 47. Testing Strategy
|
|
|
|
The lifecycle subsystem is verified by
|
|
`tests/runtime_lifecycle_test.rs`.
|
|
|
|
The documented test areas include:
|
|
|
|
- application builder lifecycle,
|
|
- state transitions,
|
|
- shutdown hook execution order,
|
|
- worker manager lifecycle.
|
|
|
|
The broader NX9-Auth test suite additionally exercises application
|
|
behavior that depends on successful runtime assembly.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 48. Lifecycle Test Model
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
TESTS["Runtime lifecycle tests"]
|
|
TESTS --> BUILDER["Builder initialization"]
|
|
TESTS --> STATES["State transitions"]
|
|
TESTS --> HOOKS["Hook ordering"]
|
|
TESTS --> WORKERS["Worker lifecycle"]
|
|
```
|
|
|
|
The tests should verify behavior rather than merely implementation
|
|
structure.
|
|
|
|
For example, hook tests should verify observable execution order instead
|
|
of assuming internal container ordering.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 49. Recommended Additional Tests
|
|
|
|
The following are valuable future lifecycle tests if not already
|
|
present:
|
|
|
|
- invalid state transition rejection,
|
|
- duplicate shutdown initiation,
|
|
- worker timeout followed by abort,
|
|
- shutdown-hook failure while later hooks still execute,
|
|
- listener bind failure,
|
|
- migration failure during initialization,
|
|
- second-signal escalation,
|
|
- cancellation propagation to child task groups,
|
|
- shutdown with zero workers,
|
|
- shutdown with zero hooks,
|
|
- repeated state reads during concurrent shutdown.
|
|
|
|
These are recommendations, not claims about the current v0.3.0 test
|
|
suite.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 50. Operational Diagnostics
|
|
|
|
When investigating startup problems, check the last lifecycle state
|
|
reached.
|
|
|
|
### Stops during `Initializing`
|
|
|
|
Investigate configuration discovery and initial runtime construction.
|
|
|
|
### Stops during `Starting`
|
|
|
|
Investigate:
|
|
|
|
- database connection,
|
|
- migrations,
|
|
- application state assembly,
|
|
- router construction,
|
|
- listener binding.
|
|
|
|
### Reaches `Running` but does not serve
|
|
|
|
Investigate:
|
|
|
|
- bound address,
|
|
- listener configuration,
|
|
- router behavior,
|
|
- reverse proxy configuration,
|
|
- firewall/network path.
|
|
|
|
### Hangs during `StoppingWorkers`
|
|
|
|
Investigate worker cancellation handling and timeout behavior.
|
|
|
|
### Hangs during `ExecutingHooks`
|
|
|
|
Investigate shutdown hooks and external dependencies used by cleanup
|
|
operations.
|
|
|
|
### Hangs during `ClosingResources`
|
|
|
|
Investigate database and other long-lived resource termination.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 51. Logging Recommendations
|
|
|
|
Lifecycle logs should be:
|
|
|
|
- structured,
|
|
- concise,
|
|
- emitted at state boundaries,
|
|
- free of credentials and secrets,
|
|
- sufficient to reconstruct startup and shutdown progression.
|
|
|
|
Recommended fields include:
|
|
|
|
``` text
|
|
event
|
|
from_state
|
|
to_state
|
|
component
|
|
duration
|
|
error
|
|
```
|
|
|
|
Sensitive configuration values must never be included merely to improve
|
|
lifecycle diagnostics.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 52. Anti-Patterns
|
|
|
|
### 52.1 Detached unmanaged tasks
|
|
|
|
Avoid spawning long-running tasks that are invisible to `WorkerManager`.
|
|
|
|
### 52.2 Independent shutdown flags
|
|
|
|
Avoid subsystem-specific boolean shutdown flags when cancellation tokens
|
|
already express the lifecycle signal.
|
|
|
|
### 52.3 Closing resources before workers
|
|
|
|
Workers may still require those resources.
|
|
|
|
### 52.4 Blocking indefinitely
|
|
|
|
Graceful shutdown requires bounded waits.
|
|
|
|
### 52.5 State regression
|
|
|
|
Do not move from a later lifecycle state back to an earlier state to
|
|
represent retries.
|
|
|
|
### 52.6 Using `force_set()` as normal control flow
|
|
|
|
Forced state mutation is an exceptional mechanism.
|
|
|
|
### 52.7 Hiding startup failure
|
|
|
|
Do not transition to `Running` if listener binding or mandatory
|
|
initialization failed.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 53. Review Checklist for Runtime Changes
|
|
|
|
Before merging changes to `src/runtime/`, verify:
|
|
|
|
- [ ] lifecycle state transitions remain valid,
|
|
- [ ] no state regression was introduced,
|
|
- [ ] shutdown remains single-initiation,
|
|
- [ ] new workers observe cancellation,
|
|
- [ ] workers are tracked and joined,
|
|
- [ ] shutdown waits are bounded,
|
|
- [ ] resources outlive dependent workers,
|
|
- [ ] hook ordering remains deterministic,
|
|
- [ ] signal handling still supports graceful termination,
|
|
- [ ] new logs contain no sensitive information,
|
|
- [ ] lifecycle tests cover new behavior,
|
|
- [ ] `cargo fmt` passes,
|
|
- [ ] `cargo clippy` passes with project warning policy,
|
|
- [ ] workspace tests pass.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 54. Source Layout
|
|
|
|
``` text
|
|
src/runtime/
|
|
├── application.rs
|
|
├── builder.rs
|
|
├── cancellation.rs
|
|
├── hooks.rs
|
|
├── lifecycle.rs
|
|
├── metrics.rs
|
|
├── mod.rs
|
|
├── signals.rs
|
|
├── state.rs
|
|
└── workers.rs
|
|
```
|
|
|
|
The exact module set should remain aligned with the repository as the
|
|
runtime evolves.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 55. Related Source Areas
|
|
|
|
The lifecycle subsystem interacts with:
|
|
|
|
``` text
|
|
src/main.rs
|
|
src/config/
|
|
src/db/
|
|
src/api/
|
|
src/middleware/
|
|
tests/runtime_lifecycle_test.rs
|
|
```
|
|
|
|
The runtime coordinates these areas but should not absorb their domain
|
|
responsibilities.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 56. Architecture Summary
|
|
|
|
``` mermaid
|
|
flowchart TD
|
|
ENTRY["CLI"] --> BUILD["Build application"]
|
|
BUILD --> READY["Dependencies ready"]
|
|
READY --> RUN["Running"]
|
|
RUN --> SIGNAL["Shutdown trigger"]
|
|
SIGNAL --> DRAIN["Draining"]
|
|
DRAIN --> WORKERS["Stopping workers"]
|
|
WORKERS --> HOOKS["Executing hooks"]
|
|
HOOKS --> RESOURCES["Closing resources"]
|
|
RESOURCES --> STOP["Stopped"]
|
|
```
|
|
|
|
NX9-Auth's lifecycle architecture is centered on a simple rule:
|
|
**construction, operation, and destruction are explicit phases with
|
|
explicit ownership**.
|
|
|
|
The state machine makes those phases observable. Cancellation tokens
|
|
propagate intent. Worker management proves task termination. Hook
|
|
priorities provide deterministic cleanup ordering. Resource closure
|
|
occurs only after dependent work has been stopped.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 57. Verification Commands
|
|
|
|
Runtime lifecycle tests:
|
|
|
|
``` bash
|
|
cargo test --test runtime_lifecycle_test
|
|
```
|
|
|
|
Complete project test suite:
|
|
|
|
``` bash
|
|
cargo test --all-features
|
|
```
|
|
|
|
Formatting:
|
|
|
|
``` bash
|
|
cargo fmt --all -- --check
|
|
```
|
|
|
|
Clippy:
|
|
|
|
``` bash
|
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
|
```
|
|
|
|
Release build:
|
|
|
|
``` bash
|
|
cargo build --release
|
|
```
|
|
|
|
For the WebAssembly administration UI, use the project's dedicated WASM
|
|
build process rather than assuming a native host build is equivalent.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 58. Document Maintenance
|
|
|
|
Update this document whenever a change modifies:
|
|
|
|
- lifecycle states,
|
|
- state transition rules,
|
|
- startup ordering,
|
|
- shutdown ordering,
|
|
- signal semantics,
|
|
- worker management,
|
|
- cancellation hierarchy,
|
|
- hook priority semantics,
|
|
- resource ownership,
|
|
- runtime metrics,
|
|
- lifecycle test guarantees.
|
|
|
|
Architecture documentation should describe implemented behavior.
|
|
Proposed behavior should be explicitly identified as a recommendation or
|
|
future design rather than presented as current implementation.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
## 59. Status
|
|
|
|
For the v0.3.0 architecture baseline, the runtime lifecycle consists of
|
|
explicit application construction, atomic lifecycle state tracking,
|
|
signal-driven graceful shutdown, hierarchical cancellation, managed
|
|
background workers, prioritized shutdown hooks, runtime metrics, and
|
|
final resource cleanup.
|
|
|
|
The lifecycle progression is:
|
|
|
|
``` text
|
|
Initializing
|
|
-> Starting
|
|
-> Running
|
|
-> Draining
|
|
-> StoppingWorkers
|
|
-> ExecutingHooks
|
|
-> ClosingResources
|
|
-> Stopped
|
|
```
|
|
|
|
This sequence forms the operational backbone of the NX9-Auth server
|
|
runtime.
|