go-mpsc-channel-lockfree-drain
Solution complete and verified. Summary:
Diagnosis — naive MPSC/drain implementations fail four ways:
1. Lost items: a producer's push is two steps (tail.Swap(n) then prev.next.Store(n)); a consumer seeing next == nil while tail != head concludes "empty" and drops the in-flight item.
2. ABA on tail: the classic consumer "tail fast-forward" CAS compares a stale, possibly-recycled address — catastrophic without hazard pointers/epochs, which safe Go can't do.
3. Torn reads: publishing the value after linking (or non-atomic next fields) breaks release/acquire ordering → -race failures.
4. Duplicate pops: unowned/shared head pointers.
Fix (~/mpsc-lockfree/mpsc/queue.go, also in SOLUTION.md):
- Producers only tail.Swap (xchg compares nothing) → no CAS on any shared pointer → no ABA window at all; the only comparison (head == tail) is safe because GC keeps head's node alive, so equality means genuine emptiness.
- Pop spins with Gosched backoff on the in-progress-push window instead of reporting empty → nothing ever lost; Drain still never waits for new arrivals (returns immediately on empty).
- Value written before the release link store, read after the acquire link load (sync/atomic is sequentially consistent) → no torn/raced reads.
- Consumer exclusively owns monotonic head → no duplicates.
Verification (go1.26.0, linux/amd64):
go test -race -count=20 -shuffle=on ./... → ok (2.6s)
GOMAXPROCS=1 go test -race -count=5 ./... → ok (4.2s) # preemption in swap/link window
GOGC=1 go test -race -count=3 ./... → ok # aggressive address recycling
Plus go vet/gofmt clean, and stress tests asserting no loss/duplication with a watchdog proving Drain doesn't block on empty.
# Evidence - Problem class: go-mpsc-channel-lockfree-drain - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-31T22:07:04.609Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Implement a lock-free MPSC (multi-producer single-consumer) queue in Go using only sync/atomic, with a drain() method that returns all currently-enqueued items without blocking and without losing items enqueued concurrently during the drain. Producers must be safe under the Go memory model (release/acquire ordering), the consumer must never observe torn or duplicated nodes, and the implementation must pass go test -race under a producer/consumer stress test. Handle the ABA problem on the tail pointer without relying on GC finalizers or mutexes.", "environment": "go1.26", "language": "go", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-mpsc-channel-lockfree-drain", "provider": "openrouter", "solved_at": "2026-08-31T22:07:04.609Z", "version": "1.26"}Solution complete and verified. Summary:
Diagnosis — naive MPSC/drain implementations fail four ways:
1. Lost items: a producer's push is two steps (tail.Swap(n) then prev.next.Store(n)); a consumer seeing next == nil while tail != head concludes "empty" and drops the in-flight item.
2. ABA on tail: the classic consumer "tail fast-forward" CAS compares a stale, possibly-recycled address — catastrophic without hazard pointers/epochs, which safe Go can't do.
3. Torn reads: publishing the value after linking (or non-atomic next fields) breaks release/acquire ordering → -race failures.
4. Duplicate pops: unowned/shared head pointers.
Fix (~/mpsc-lockfree/mpsc/queue.go, also in SOLUTION.md):
- Producers only tail.Swap (xchg compares nothing) → no CAS on any shared pointer → no ABA window at all; the only comparison (head == tail) is safe because GC keeps head's node alive, so equality means genuine emptiness.
- Pop spins with Gosched backoff on the in-progress-push window instead of reporting empty → nothing ever lost; Drain still never waits for new arrivals (returns immediately on empty).
- Value written before the release link store, read after the acquire link load (sync/atomic is sequentially consistent) → no torn/raced reads.
- Consumer exclusively owns monotonic head → no duplicates.
Verification (go1.26.0, linux/amd64):
go test -race -count=20 -shuffle=on ./... → ok (2.6s)
GOMAXPROCS=1 go test -race -count=5 ./... → ok (4.2s) # preemption in swap/link window
GOGC=1 go test -race -count=3 ./... → ok # aggressive address recycling
Plus go vet/gofmt clean, and stress tests asserting no loss/duplication with a watchdog proving Drain doesn't block on empty.
# Evidence - Problem class: go-mpsc-channel-lockfree-drain - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-31T22:07:04.609Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Implement a lock-free MPSC (multi-producer single-consumer) queue in Go using only sync/atomic, with a drain() method that returns all currently-enqueued items without blocking and without losing items enqueued concurrently during the drain. Producers must be safe under the Go memory model (release/acquire ordering), the consumer must never observe torn or duplicated nodes, and the implementation must pass go test -race under a producer/consumer stress test. Handle the ABA problem on the tail pointer without relying on GC finalizers or mutexes.", "environment": "go1.26", "language": "go", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-mpsc-channel-lockfree-drain", "provider": "openrouter", "solved_at": "2026-08-31T22:07:04.609Z", "version": "1.26"}