Proofs: Disruptor ring buffer
These are the human proofs of Disruptor ring buffer, at full length.
The Disruptor ring buffer is a linearizable bounded broadcast channel full
This proof shows: correctness, linearizability, progress.
We use the pseudocode of the page, with ring size . Every read and write of one sequence number, and the fetch-and-add of line L1, is atomic, and reads see the last write (sequential consistency of the shared words). Each consumer is one thread, so its calls of CONSUME do not overlap; it is the only writer of . The dependency graph of the consumers has no cycle. Let be the item of the producer that got at line L1.
Lemma 1 (monotone sequences). , and every never decrease. The cursor takes the values in this order, and holds exactly from the moment the producer of runs line L5.
Proof. Line L1 only adds. A producer with number sets the cursor to at line L5 only after it has read the value at line L4, and no other producer can write the cursor in between: a write of needs the value , the numbers are distinct, and the only producer that waits for the value is the one with . So each write raises the cursor by 1. Consumer sets at line L9 to , read at line L6, and nobody else writes between lines L6 and L9.
Lemma 2 (reads of a minimum). If a thread reads several monotone words one after another and computes the minimum of the values, then is at most the true minimum of the words at the time of the last read.
Proof. Each value read is at most the current value of its word at every later time, so the minimum of the values is at most the minimum of the current values at the time of the last read.
Lemma 3 (invariant). At all times, for each consumer and each , . Also, a producer passes line L2 with number only when for all consumers .
Proof. The value written at line L9 is at most the cursor and each at the time of the reads of line L7 (Lemma 2 for the minimum), and by Lemma 1 these values only grow until line L9. So the inequalities hold after each write of , and writes of other words only make them easier. The last claim is the condition of line L2 together with Lemma 2.
Lemma 4 (slots). If a consumer reads slot at line L8 in a call with , it reads .
Proof. At line L7, read a value at least from the cursor, so by Lemma 1 the producer of has run line L5, and hence line L3 before it: the slot got . The only later writes to this slot are by producers of with at line L3, after they passed line L2. By Lemma 3, that needs to be at most at some time, so . But during this call until line L9, which comes after line L8. So the slot still holds when reads it.
The abstraction. At each moment, let be the list , and . Initially is empty and every . Only line L5 changes , and only line L9 changes .
Theorem (linearizability). Each history of the ring buffer is linearizable with respect to the sequential broadcast channel of the page.
Proof. Linearize each publish that has run line L5 at that step, and each consume that has run line L9 at that step; remove other pending calls. Each point lies inside its call, so the order of the points extends real-time order. We show that, at each point, the abstract operation is allowed and gives the same change and result.
A publish with number at line L5. By Lemma 1, before this step , so , and the step appends , the abstract publish, with result after the append. The abstract precondition , that is , held when the producer passed line L2 (Lemma 3), and it still holds at line L5 because every only grows.
A consume of at line L9, with and as in the code. Then before the step, and . At line L7, was at most the cursor and every for (Lemma 2), and these only grow, so at line L9 and . So the abstract consume may choose the end , and it sets to , as line L9 does. By Lemma 4 the batch returned is , which is , the abstract result.
Theorem (progress). No thread holds a lock. A thread waits only at line L2, line L4 or line L7, and:
- A consume of at line L7 passes within of its own steps after a moment from which the abstract consume of is allowed, that is, .
- A publish at line L2 passes within of its own steps, for consumers, after a moment from which the gate condition holds; with one producer this is exactly the abstract condition that the ring is not full.
- A publish with number waits at line L4 only until the producers of all smaller numbers have run line L5. With one producer, it never waits at line L4.
- So with one producer, every call finishes within of its own steps, where is the batch size, once its abstract operation is allowed: the structure is wait-free apart from the waits that the abstract type itself demands.
Proof. Part 1: from that moment on, each reading of line L7 gives a minimum of at least the values at its first read, which by Lemma 1 are at least those at the moment, so the test passes on the next round, after steps. Lines L8 to L10 take steps. Part 2 is the same argument for line L2. With one producer, while the producer is outside a call, so and the gate condition is the abstract condition. Part 3 is Lemma 1: the cursor reaches when the producer of runs line L5, and by induction this needs the producers of all smaller numbers. With one producer, all smaller numbers were published by its earlier calls. Part 4 combines parts 1 to 3.
With many producers the structure is blocking: a producer that stops between lines L1 and L5 stops all producers with larger numbers at line L4, and, once the ring fills, all consumers at line L7. If no thread stops and every consumer keeps calling CONSUME, every publish finishes. Take the smallest pending number . All smaller numbers are published, so and the producer of waits only at line L2. Go through the consumers in an order in which each comes after its dependencies, which exists since the graph has no cycle. A consumer with no dependencies sees the cursor at and, by part 1, reaches within its next calls; a consumer whose dependencies have all reached does the same. So every reaches , the gate opens, and by part 2 and part 3 the publish of finishes. By induction on every publish finishes.