Files
kacerrandClaude Fable 5 7219eb5a23 Initial commit: Go SRE interview exercises
Includes solved 02-worker-pool and 09-safe-counter (mutex, atomic, sharded).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq
2026-08-28 22:18:09 +02:00

66 lines
2.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 07 — Fan-in (Merge)
The mirror image of the worker pool: N producers, one stream. Every metrics
pipeline, event aggregator, and multi-shard reader is this shape.
## The double-select most solutions miss
A forwarder that's correct under cancellation needs `ctx.Done()` in **two**
places:
```go
for {
v, ok := <-ch // (1) can block forever if input never closes
if !ok { return }
out <- v // (2) can block forever if consumer walked away
}
```
Both blocking points must become selects. Miss (2) and you have the sneakier
leak: the consumer stops reading (their own ctx died), your forwarders sit
parked on `out <- v` until process exit. The test cancels *while* you're
blocked on send, specifically to catch this. In pprof this leak has a
signature: dozens of goroutines parked in `chan send` inside your merge
function, ages in the minutes column.
## `runtime.NumGoroutine()` as a test assertion
Counting goroutines before/after is a legit leak-detection technique — crude
but effective. The polished version is `goleak` (from Uber), worth namedropping:
it snapshots goroutines at test end and fails on strays. In production the
equivalent is watching `go_goroutines` from the Prometheus client — a sawtooth
that only goes up is a leak with a deadline.
## Nil-channel select: the elegant alternative
For a *small, fixed* number of inputs there's a beautiful trick — select over
all inputs directly and set a channel variable to `nil` when it closes:
```go
for a != nil || b != nil {
select {
case v, ok := <-a:
if !ok { a = nil; continue } // case permanently disabled
out <- v
case v, ok := <-b:
if !ok { b = nil; continue }
out <- v
}
}
```
Ops on nil channels block forever, so a nil case simply never fires — it's the
idiomatic way to *disable* a select arm at runtime. This version needs no
WaitGroup and no forwarder goroutines. Know both: goroutine-per-input scales
to dynamic N; nil-channel select wins for 2–3 known inputs. Explaining *why*
the nil trick works (nil channel semantics) is exactly the kind of
language-lawyer flex these interviews reward.
## Bonus rabbit hole: what select actually does
With multiple ready cases, `select` picks **uniformly at random** — the runtime
shuffles case order (`selectgo`) to prevent starvation. So merge output
ordering isn't just "unspecified," it's actively randomized under contention.
Determinism-dependent tests on select ordering are therefore always flaky —
a code-review comment you'll get to make at least once a year for free.