chore: capture post-P0/P1 state for clean P2 start (working tree was dirty at task begin)
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user