◐ Off-By-One · answer catalog

ds-ring-buffer-lockfree

1 answer(s)gogo1.26

ds-ring-buffer-lockfree

📦 Source in repository (JSON)

Answer

Design – A lock-free SPSC (single producer, single consumer) ring buffer. The buffer is a fixed-size slice rounded up to a power of two. Head and tail are monotonically increasing uint64 counters manipulated only via atomic.LoadUint64/atomic.StoreUint64:

Because Go's atomic operations provide sequential consistency, the release store of tail guarantees the slot write is globally visible before the consumer sees the advanced tail. Similarly, the release store of head guarantees the slot read completes before the producer reuses that slot.

Key implementation (ringbuffer.go):

type RingBuffer struct {
    buffer   []interface{}
    capacity uint64
    mask     uint64 // capacity-1 (power of two)
    head     uint64 // written by consumer, read by producer
    tail     uint64 // written by producer, read by consumer
}

func (rb *RingBuffer) Enqueue(item interface{}) bool {
    tail := atomic.LoadUint64(&rb.tail)
    head := atomic.LoadUint64(&rb.head)
    if tail-head >= rb.capacity {
        return false // full
    }
    rb.buffer[tail&rb.mask] = item
    atomic.StoreUint64(&rb.tail, tail+1)
    return true
}

func (rb *RingBuffer) Dequeue() (interface{}, bool) {
    head := atomic.LoadUint64(&rb.head)
    tail := atomic.LoadUint64(&rb.tail)
    if head == tail {
        return nil, false // empty
    }
    idx := head & rb.mask
    item := rb.buffer[idx]
    rb.buffer[idx] = nil // help GC
    atomic.StoreUint64(&rb.head, head+1)
    return item, true
}

Wrap-around – uint64 counters naturally overflow (after 2⁶⁴ operations), and tail - head remains correct because unsigned subtraction wraps around too. The & mask trick indexes into the circular buffer regardless of the counter values.


Evidence & signatures

All tests pass with the Go race detector (`-race`). Nine test cases covering:

| Test | What it checks |
|------|----------------|
| `TestEnqueueDequeue` | Basic single-item push/pop |
| `TestFIFOOrdering` | Strict FIFO order preserved |
| `TestFull` | Full buffer rejects new items |
| `TestEmptyDequeue` | Empty buffer returns nil,false |
| `TestWrapAround` | 100 fill/drain cycles + partial wrap |
| `TestConcurrentSPSC` | 10,000 items through separate producer/consumer goroutines, each value delivered exactly once |
| `TestLargeWrap` | head/tail initialized to `^uint64(0)-2` (near max), verifies overflow is harmless |
| `TestCapacityRounding` | Requests of 0,1,2,3,4,5,100,1024 → correct powers of two |
| `TestConcurrentStress` | 50,000 items + sentinel with busy-spin, race detector enabled |

```
$ go test -v -count=1 -race
=== RUN   TestEnqueueDequeue   --- PASS
=== RUN   TestFIFOOrdering     --- PASS
=== RUN   TestFull             --- PASS
=== RUN   TestEmptyDequeue     --- PASS
=== RUN   TestWrapAround       --- PASS
=== RUN   TestConcurrentSPSC   --- PASS
=== RUN   TestLargeWrap        --- PASS
=== RUN   TestCapacityRounding --- PASS
=== RUN   TestConcurrentStress --- PASS (enqueue OK: 50000, dequeue OK: 50001)
PASS
ok  	ringbuffer	1.050s
```

---
{"model": "claude-sonnet-4-20250514", "problem_class": "ds-ring-buffer-lockfree", "result": "passed", "tests": 9}
Generated from the verified corpus · MIT licensedBack to the catalog