# P0: Critical Hardening & Stability Fixes **Priority**: P0 — Ship-blocking **Theme**: Eliminate crashes, goroutine leaks, and silent misconfigurations that make the service unreliable as a long-running LAN cache appliance. **Status**: Not started **Target**: All items resolved before next production deployment or public release. ## Goal Make `steamcache2` safe to run continuously with real traffic under normal and adverse conditions (bad config, unreachable upstream, frequent restarts, high load). ## Overview The current implementation has several classes of defects that can cause: - Immediate panics on startup or misconfiguration - Deadlocks / hangs on graceful shutdown - Silent failure to enforce documented configuration constraints - Loss of error visibility in metrics and logs These must be fixed before the project can be considered production-ready. ## Tasks ### P0-01: Make `New()` return an error instead of panicking on invalid sizes - **Description**: `units.FromHumanSize()` failures in `New()` currently call `panic()`. This is hostile to callers (tests, embedding, future CLI refactoring) and prevents clean error handling. - **Impact**: Any invalid memory/disk size string (including from generated default config in edge cases) crashes the entire process with a stack trace instead of a helpful message. - **Affected Files**: - `steamcache/steamcache.go` (New function, lines ~854-863) - Call sites in `cmd/root.go` - All tests that construct `SteamCache` - **Approach**: 1. Change signature to `func New(...) (*SteamCache, error)` 2. Return wrapped error for parse failures. 3. Update `NewWithOptions` accordingly. 4. Update all internal construction paths and tests (use `t.Cleanup` + proper error checks). - **Acceptance Criteria**: - No more panics from `New()` for bad size strings. - Clear error messages: `"invalid memory size: ..."` - All existing tests still pass (updated for new signature). - `go test -race -short ./...` is green. - **Dependencies**: None - **Effort**: Small (1-2 hours) ### P0-02: Fix upstream connectivity check nil pointer dereference - **Description**: In `Run()`, the code does: ```go resp, err := sc.client.Get(sc.upstream) if err != nil || resp.StatusCode != http.StatusOK { ... use resp.StatusCode when err != nil ... ``` When the GET fails, `resp` is typically `nil`. - **Impact**: Service crashes with panic on startup whenever the configured `upstream` is unreachable or returns non-200 (very common on first setup or network hiccup). - **Affected Files**: - `steamcache/steamcache.go` (Run method, ~1025-1032) - **Approach**: 1. Reorder the check: handle `err != nil` first. 2. Only inspect `resp.StatusCode` when `resp != nil`. 3. Always close `resp.Body` when `resp != nil`. 4. Improve the error log message. 5. Consider making the upstream check optional or retrying (but keep current behavior of exiting for now). - **Acceptance Criteria**: - Starting with an unreachable upstream produces a clean error log + `os.Exit(1)` instead of a panic. - Starting with a reachable but non-200 upstream behaves cleanly. - No resource leaks in the error paths. - **Dependencies**: None - **Effort**: Trivial (< 30 min) ### P0-03: Call `config.Validate()` on startup and fail fast with actionable messages - **Description**: A complete `Validate()` method exists in `config/config.go` but is **never invoked**. - **Impact**: - Users can start the service with invalid GC algorithm names, negative concurrency limits, or disk cache enabled with no path. - The service runs in a broken or surprising state instead of failing early with a clear message. - **Affected Files**: - `config/config.go` - `cmd/root.go` (after `LoadConfig`) - **Approach**: 1. Call `cfg.Validate()` immediately after loading (and after applying CLI overrides). 2. On error, log the problem at ERROR level with the exact field and suggestion. 3. Exit with code 1 and print a user-friendly message to stderr. 4. Add unit tests for `Validate()` covering all error cases (currently untested). - **Acceptance Criteria**: - Invalid `gc_algorithm`, negative limits, or missing disk path cause immediate clean failure. - Error messages are specific and actionable. - `Validate()` has ≥90% statement coverage in its own test file. - **Dependencies**: None - **Effort**: Small (1-2 hours including tests) ### P0-04: Wire up error metrics and ensure all error paths are instrumented - **Description**: `metrics.IncrementErrors()` exists and `Stats.Errors` is exposed, but the method is **never called** anywhere in the codebase. Many 5xx paths also fail to increment other relevant counters. - **Impact**: Operators have no visibility into error rates via `/metrics`. The "errors" field is always zero. - **Affected Files**: - `steamcache/metrics/metrics.go` - `steamcache/steamcache.go` (ServeHTTP and related methods — many locations) - Possibly `vfs/*` error paths that bubble up - **Approach**: 1. Audit every place that returns 5xx or logs an error in the request path. 2. Call `sc.metrics.IncrementErrors()` (and any other appropriate counters) in those paths. 3. Ensure coalesced request error paths also record errors. 4. Add a simple test that exercises error paths and asserts metric values. - **Acceptance Criteria**: - `/metrics` reports non-zero `errors` under induced failure conditions. - All current 5xx response paths increment the counter exactly once per failed request. - No double-counting on coalesced failures. - **Dependencies**: P0-02 (partially) - **Effort**: Medium (2-4 hours) ## Definition of Done (for the whole P0 milestone) - [ ] All four tasks above completed and merged. - [ ] `go test -race -shuffle=on -timeout=5m ./...` passes cleanly. - [ ] Manual verification: - Start with bad memory size → clean error, no panic. - Start with unreachable upstream → clean error + exit 1, no panic. - Start with invalid `gc_algorithm` → fails fast with clear message. - Induce upstream 500s and connection errors → `/metrics` shows increasing errors count. - [ ] Shutdown no longer hangs due to the client limiter goroutine (see P0-05 below if split out). - [ ] Updated README or a new `docs/OPERATIONS.md` section documents the new strict startup validation behavior. ## Notes for Implementers - These fixes are intentionally small and localized so they can be landed quickly. - Prefer adding new tests over modifying large amounts of existing test code. - Keep backward compatibility for the public `New` constructor as much as possible (or provide a clear migration path in comments). ## References - Full code review (see conversation history or `plans/` directory). - Original locations identified in `steamcache/steamcache.go`, `config/config.go`, `cmd/root.go`. - Related goroutine leak in `cleanupOldClientLimiters` (may be promoted to its own P0 item if it blocks shutdown testing). --- **Next actions after P0**: Move on to P1 items once the service can start and stop reliably without crashing.