# Phase 10: Main Entrypoint — COMPLETED

## Goal
Wire all subsystems together, start the server, handle shutdown.

## Files Modified
- `src/server/tcp.rs` — Refactored `TcpServer::new()` to accept external `message_id_counter`
- `tests/common/mod.rs` — Updated test helper to pass `message_id_counter`
- `src/config.rs` — Added `QueueConfig` struct (`max_depth`, `backpressure_policy`, `max_redelivery_attempts`, `dlq_topic_suffix`)
- `src/queue/topic.rs` — Added `serde::Deserialize` to `BackpressurePolicy`
- `config/config.example.toml` — Added `[queue]` section
- `src/main.rs` — Complete rewrite as async entrypoint

## Pre-work Completed

### Shared message_id_counter — TcpServer constructor refactor
`HttpServer::new()` accepted an external `Arc<AtomicU64>` for message IDs, but
`TcpServer::new()` created its own internally (`server/tcp.rs:47`). Refactored
`TcpServer::new()` to accept `message_id_counter: Arc<AtomicU64>` as a parameter
(matching the HTTP server pattern). `client_id_counter` stays internal — only
used by the TCP accept loop.

### QueueConfig added to ServerConfig
`InMemoryQueueEngine::new()` requires `default_max_depth` and `BackpressurePolicy`,
and `DefaultNamespaceManager::new()` requires `max_redelivery_attempts` and
`dlq_topic_suffix`. These were not previously in config. Added a `[queue]` config
section with all four fields, with sensible defaults:
- `max_depth`: 100,000
- `backpressure_policy`: `DropOldest`
- `max_redelivery_attempts`: 5
- `dlq_topic_suffix`: `"__dlq"`

This replaces the original plan's approach of using the first namespace's values
(which was non-deterministic due to `HashMap` iteration order).

## Completed Work

### main.rs — Async Entrypoint
- [x] `#[tokio::main]` async entrypoint
- [x] `tracing_subscriber::fmt::init()` for structured logging
- [x] Parse CLI args via `CliArgs::parse()`, load config via `ServerConfig::load()`
- [x] Create `FsStorage`, call `storage.init()` (creates data dir, pre-populates segment writers)
- [x] Create `InMemoryQueueEngine` from `config.queue` settings, call `engine.init()` (replays persisted topics, starts flush task)
- [x] Create `ApiKeyStore` from config
- [x] Create `DefaultNamespaceManager` with `config.queue.max_redelivery_attempts` and `config.queue.dlq_topic_suffix`
- [x] Shared `watch::channel(false)` for shutdown signaling
- [x] Shared `Arc<AtomicU64>` message ID counter (starting at 1) passed to both servers
- [x] Spawn TCP and HTTP servers as concurrent tasks
- [x] `tokio::select!` on SIGINT (ctrl+c), SIGTERM, and server task handles
- [x] Non-zero exit code (1) when a server exits unexpectedly
- [x] Graceful shutdown with 10-second timeout (force-exits on timeout)

### Startup Sequence (as implemented)
1. Init tracing
2. Parse CLI args, load config
3. Create `FsStorage` from `config.storage.data_dir`
4. Call `storage.init()` — creates data dir, pre-populates segment writer cache
5. Create `InMemoryQueueEngine` with `config.queue.max_depth` and `config.queue.backpressure_policy`
6. Call `engine.init()` — replays all persisted topics, starts background flush task
7. Create `ApiKeyStore` from config
8. Create `DefaultNamespaceManager` with engine, namespace IDs, redelivery config from `config.queue`
9. Create shared `watch::channel(false)` for shutdown signaling
10. Create shared `Arc<AtomicU64>` message ID counter
11. Create TCP server (with namespace manager, auth, message_id_counter, shutdown_rx)
12. Create HTTP server (with namespace manager, auth, message_id_counter, shutdown_rx)
13. Spawn both servers as tokio tasks
14. Await shutdown signal (SIGINT, SIGTERM, or unexpected server exit)

### Shutdown Sequence (as implemented)
1. Send `true` on shutdown `watch` channel — servers stop accepting connections
2. Start 10-second timeout
3. Call `namespace_manager.shutdown()` — signals all delivery tasks, aborts handles
4. Call `queue_engine.shutdown()` — flushes storage, stops background flush task
5. If timeout expires: log error and force-exit with code 1
6. Otherwise: log "shutdown complete", exit with code 0 (or 1 if server exited unexpectedly)

### Signal Handling
- **SIGINT** (ctrl+c): Graceful shutdown, exit code 0
- **SIGTERM** (`docker stop`, `kill`, systemd): Graceful shutdown, exit code 0
- **Server task exit**: Log error details from `JoinHandle`, exit code 1

### Config Additions (`src/config.rs`)
```rust
pub struct QueueConfig {
    pub max_depth: usize,                       // default: 100_000
    pub backpressure_policy: BackpressurePolicy, // default: DropOldest
    pub max_redelivery_attempts: u32,            // default: 5
    pub dlq_topic_suffix: String,                // default: "__dlq"
}
```

Added `Deserialize` to `BackpressurePolicy` enum (`src/queue/topic.rs`).

## Key Decisions
- `#[tokio::main]` async entrypoint — all subsystems are async
- Both servers run concurrently via `tokio::select!` on their `JoinHandle`s
- Shared state via `Arc<NamespaceManager>` passed to both servers
- Single `watch::channel` drives shutdown for both servers and all delivery tasks
- Single `Arc<AtomicU64>` message ID counter shared across TCP and HTTP
- Queue engine settings are server-level config, not per-namespace — avoids non-deterministic `HashMap` iteration order
- SIGTERM handled for container/systemd deployments
- 10-second shutdown timeout prevents hung processes
- Non-zero exit code on unexpected server exit

## Open Questions (deferred)
- Startup health checks (e.g., verify storage dir writable before accepting connections)
- PID file support
- Daemonization — rely on systemd/docker instead
- Per-namespace queue config overrides (currently global via `[queue]`)

## Done When
- [x] Server starts from example config
- [x] TCP and HTTP both accepting connections
- [x] Ctrl+C triggers clean shutdown
- [x] SIGTERM triggers clean shutdown
- [x] Startup logs show config loaded, storage replayed, servers bound
- [x] Shutdown timeout prevents hung process
- [x] Non-zero exit on server crash
- [x] 263 tests pass, clippy clean, release build succeeds
