diff --git a/README.md b/README.md index e65da8a..f85b103 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- wirefan + wirefan

# wirefan @@ -35,12 +35,12 @@ Full walk-through in [`ARCHITECTURE.md`](ARCHITECTURE.md#quickstart-for-contribu Two planes share one process: -- **Data plane** — `/v1/connect` upgrades to WebSocket; clients `subscribe` / - `publish` JSON frames; the `Fanout` strategy pushes payloads to every +- **Data plane:** `/v1/connect` upgrades to WebSocket; clients `subscribe` and + `publish` JSON frames; `hub.Broadcast` pushes each payload to every subscriber on the channel. -- **Control plane** — `/v1/keys` (admin Bearer) mints API keys; `/v1/auth/sign` - issues HMAC tokens for `private-*` channels; `/metrics` exposes Prometheus - collectors; `/debug/pprof/*` is wired for production debugging. +- **Control plane:** `/v1/keys` (admin Bearer) mints API keys; `/v1/auth/sign` + issues HMAC tokens for `private-*` channels; a separate admin listener serves + `/metrics` (Prometheus) and `/debug/pprof/*` for production debugging. Swappable interfaces (`Fanout`, `Registry`, `Store`, backpressure `Policy`) mean the same wire path benchmarks two strategies. See @@ -53,16 +53,19 @@ and [`docs/DESIGN.md`](docs/DESIGN.md) for the rationale behind each choice. Methodology, the `Fanout × Registry` matrix runner (`scripts/bench.sh`), and the `cmd/loadtest` driver are in place. Reproducible numbers from a 1-vCPU -ARM reference box are pending — the project is currently undeployed. See +ARM reference box are pending, since the project is currently undeployed. See [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) for the methodology and target -matrix; numbers will be filled in after the first hosted deploy. +matrix; numbers get filled in after the first hosted deploy. What's verified today: - Goroutine-leak invariant: 1000-connection churn under `-race` returns to baseline within tolerance (`internal/server/leak_test.go`). -- Per-channel FIFO ordering: enforced via per-channel broadcast mutex, - serialized iteration, and a publisher-monotonic test. +- Per-subscriber FIFO ordering: each connection's buffered send channel + preserves order (Go guarantees send order equals receive order). `Broadcast` + snapshots subscribers under `RLock` then sends concurrently, so per-channel + total ordering is intentionally not a protocol guarantee (see the rationale + below). - Graceful shutdown: 30 s drain, `WaitGroup`-tracked goroutines, no leaks (covered by `internal/server/shutdown_test.go`). @@ -88,31 +91,34 @@ Full message catalog, error codes, close codes, and the HMAC flow for ## Why these choices -- **`coder/websocket` over `gorilla/websocket`** — actively maintained - successor with a smaller, context-aware API; gorilla is in archive mode. -- **SQLite over Postgres** — single-file durability, zero ops; Postgres is +- **`coder/websocket`, not `gorilla/websocket`.** The actively maintained + successor, with a smaller, context-aware API. gorilla is in archive mode. +- **SQLite, not Postgres.** Single-file durability, zero ops. Postgres is out of scope for V1 (single-server deployment). -- **Per-channel mutex over lock-free** — FIFO ordering is trivial to reason - about and prove; the contention boundary is one channel, not one server. -- **HMAC channel tokens, server-only signing secret** — no per-key crypto - material on disk; tokens are bound to `socket_id` so leaks can't be +- **Concurrent broadcast, not a per-channel lock.** `Broadcast` snapshots + subscribers under `RLock` then sends concurrently. Each connection's buffered + channel keeps its own order, so per-subscriber FIFO holds. A per-channel + broadcast mutex was removed because one slow consumer head-of-line-blocked the + whole channel under the disconnect policy's write deadline. +- **HMAC channel tokens with a server-only signing secret.** No per-key crypto + material on disk, and tokens are bound to `socket_id` so a leak can't be replayed on a different connection. -- **Pluggable `Fanout` and `Registry`** — same code path benchmarks two +- **Pluggable `Fanout` and `Registry`.** The same code path benchmarks two strategies (per-conn goroutine vs sharded worker pool; `sync.Map` vs - sharded RWMutex). Default ships per-conn fanout + sync-map registry. + sharded RWMutex). The default ships per-conn fanout with a sync-map registry. --- ## Deferred (next steps) - Multi-server scaling via Redis pub-sub (single-server is V1 scope) -- Message history / replay (`Last-Event-ID` style) +- Message history and replay (`Last-Event-ID` style) - Presence with join/leave diffs - Polished client SDK (raw WebSocket only for now) --- -## Build & test +## Build and test ```bash make build # -> bin/wirefan @@ -130,10 +136,10 @@ underlying binaries build and run on every Go target. ## Docs -- [`ARCHITECTURE.md`](ARCHITECTURE.md) — repo map, request lifecycle, where to look when -- [`docs/DESIGN.md`](docs/DESIGN.md) — architectural decisions, alternatives, scaling roadmap -- [`docs/PROTOCOL.md`](docs/PROTOCOL.md) — wire format spec, frame schemas, error codes -- [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) — methodology + results +- [`ARCHITECTURE.md`](ARCHITECTURE.md): repo map, request lifecycle, where to look when +- [`docs/DESIGN.md`](docs/DESIGN.md): architectural decisions, alternatives, scaling roadmap +- [`docs/PROTOCOL.md`](docs/PROTOCOL.md): wire format spec, frame schemas, error codes +- [`docs/BENCHMARKS.md`](docs/BENCHMARKS.md): methodology and results --- diff --git a/docs/social.png b/docs/social.png new file mode 100644 index 0000000..0e94185 Binary files /dev/null and b/docs/social.png differ