Skip to content

Build1 publisher2 min readPublished

One modulus check made a full four-slot ring report empty

Full and empty share a residue once the cursors lap, so a four-slot ring reports empty with four live ints in it, and the sanitizers have nothing to say because nothing illegal happened.

The Engineer · Build desk

Illustration accompanying One modulus check made a full four-slot ring report empty

What happened

  • A four-slot ring in the demo takes four pushes with no pops, and the print shows empty=true with the write cursor at four and the read cursor at zero.
  • The drain loop that follows pops nothing at all, reporting popped=0 while four written ints are still sitting in the buffer.
  • empty() compares w % cap against r % cap, and at that moment both residues are zero, so the full ring and an empty one produce the same answer.
  • The file is built with g++ at -O1 under the address and undefined-behaviour sanitizers, and it runs to completion with no crash and no diagnostic.
  • The author writes that the ring reported empty after a full lap, that every payload was lost, and that CI never failed an assert on the path.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A suite that exercises partial fills can pass forever without touching the failing state, because only the fill to exactly capacity produces the collision.
  • cost The cheapest fix charges a slot per ring: reserve the gap and a four-element buffer carries three items, and the boundary test has to move with it.
  • decision Since the tooling only watches memory, the fullness invariant has to be asserted in the code itself, by whoever owns the queue.
  • contradiction Treat the code as reproducible and the payload loss as the author's own account: the post labels its test list proposed, not a production incident dump.

`(w % cap) == (r % cap)` is true whenever `w - r` is an exact multiple of `cap`. On a four-slot ring that covers occupancy zero, four and eight [19]. Zero is empty. Four is full, and the modulus has already thrown away the lap count that separated them [1].

So the failing input is one point per lap. Fills of one, two or three items answer correctly; only the fill to exactly four collides [20]. At capacity eight it is one level in eight [20]. The post says most of its tests stopped one below capacity [10], and describes the happy path as never filling the whole buffer [22]. Huge rings hide the same collision for days, which is why the smallest ring is the fastest way to see it [23].

`push` stores into `buf[w % cap]` and then increments `w` [17], and `buf` holds exactly `cap` elements, so every store is inside the allocation and the address sanitizer has a legal write to look at [24]. Nothing in the run is undefined, so UBSan stays quiet as well [6]. "Sanitizers do not know your ring protocol. They only know invalid memory use here," the post says [7]. The one automated signal in the repro is the exit status, and it is there because `main` returns `n == 0 ? 1 : 0` [8]. There was one thread and one tight loop, so nothing here is a race [22].

Three other designs were weighed before the occupancy count. A boolean `is_empty` flag drifts as soon as two writers exist [12]. Subtracting 32-bit cursors wraps too, just much later [13]. Keeping the modulus and reserving one unused slot works if full is actually tested [14], and it costs a slot: a four-element buffer then holds three items [21].

The repro transfers to any ring that stores unbounded cursors, reduces them only inside the predicate, and leaves no gap slot [1]. A ring that answers from occupancy instead is not exposed, because `w - r` is four when four items are live [11][18]. The order the post recommends for the hunt is to shrink capacity to four or eight, drive exact fills at cap-1, cap and cap+1, record cursors on every push and pop, assert `w - r` against the visible occupancy, and add threads only after the single-threaded check is strict [15].

What to watch

  • Whether the post publishes the full occupancy-based Ring with a passing test that fills to exactly cap and to cap+1.
  • Whether the same reduced-cursor empty predicate turns up in a widely used queue or logging library rather than a demo file.
  • Whether any static analyser or sanitizer mode learns to flag comparing two reduced cursors as a fullness test.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories