Proofs: LCRQ (linked concurrent ring queue)
These are the human proofs of LCRQ (linked concurrent ring queue), at full length.
The LCRQ is a linearizable, lock-free FIFO queue full
This proof shows: correctness, linearizability, progress.
We use the pseudocode of the page. Reads, the fetch-and-add and the closing of a tail word, the fetch-and-add of a head word, the CAS2 of a cell and the CAS of a pointer are atomic. Items are distinct (tag them with the enqueue that made them). The specification is the sequential FIFO queue, where dequeue returns EMPTY on an empty queue.
Part 1: one CRQ
Fix a CRQ with ring size . An enqueue attempt with ticket is one round of lines L1 to L6 whose fetch-and-add returned with the bit open; a dequeue attempt with ticket is one round of lines L7 to L14. Tickets are distinct for each counter, since fetch-and-add hands out each value once. A cell serves tickets ; its round is its .
Lemma 1 (cells move forward). Every successful CAS2 on cell keeps and either raises or keeps and changes in one of two ways: with set to 1 (line L5, which may also raise ), or from 1 to 0 with the same item (line L11). A cell never returns to a state it had before, so a CAS2 that succeeds compares against the state that was read, with no ABA.
Proof. Line L5 replaces by with and . Lines L10 and L12 set to , since there (line L9 exits otherwise). Line L11 only clears . For a fixed , the state can go from to an item once (afterwards line L5 needs , and the only way back to is lines L10 and L12, which raise ), and from to once. So the sequence of states of the cell is strictly increasing in the order (idx, then val set, then safe cleared, the latter two in either order), and no state repeats.
Lemma 2 (who takes an item). An item stored by the enqueue attempt with ticket (line L5, giving ) is removed from its cell only by line L10 of the dequeue attempt with ticket .
Proof. Only line L10 removes an item, and it requires . After line L5 the stays until the item is removed (only lines L10 and L12 raise it, and line L12 needs ). So .
Lemma 3 (tickets match). Let be an enqueue attempt with ticket and let be the dequeue attempt with ticket , if any. (a) If stores its item, then , if it runs line L8 at all, returns that item. (b) If exits its inner loop without an item, then does not store, either before or after.
Proof. Consider the moment when cell first has , or first holds the item of ticket ; one of these happens before either attempt can succeed with ticket , and by Lemma 1 they cannot both precede each other.
(a) Suppose stored, so from that CAS2 on. By Lemma 1 the of stays until is removed, and by Lemma 2 only removes it. When reads the cell, either the item is there with , and line L10 is tried; a failed CAS2 means the cell changed, which only itself or line L11 can cause (clearing keeps and ), so retries and eventually succeeds, since line L11 succeeds at most once. Or the cell is earlier than 's CAS2 in Lemma 1's order, with ; then tries line L11 or L12. Line L12 with and would raise above before 's CAS2, which then fails, a contradiction; so that CAS2 of fails, and rereads. Line L11 needs an item of an older round, . A success of line L11 makes give up, so we must exclude it: after it, the cell holds an item with and , and when its dequeuer takes it, the cell becomes (line L10 gives , the only round between). Now can store only through line L4 with , which needs ; but took ticket before line L11, so from then on. So cannot store after the success of line L11, and it did not store before it, since the cell then held an older item. This contradicts the assumption that stored, so line L11 does not succeed for , and returns .
(b) exits in three ways. By line L9, the cell had ; by Lemma 1 it keeps , and 's line L4 or L5 fails from then on; and did not store before, since then would have stayed until removed the item. By line L12, the same holds from that CAS2 on. By line L11, the argument of (a) shows that cannot store afterwards, and it did not store before, since the cell held an older item until then and line L5 needs .
Lemma 4 (closed means closed). Once the closed bit of is set, every later attempt returns CLOSED at line L2 and stores nothing. FIX-STATE never clears the bit.
Proof. The bit is cleared by no step: fetch-and-add keeps it, and line L15 CASes only from an open tail to an open tail.
Lemma 5 (empty). If a dequeue attempt with ticket reads at line L13 a tail counter at time , then every enqueue attempt that has taken a ticket before has , and each such attempt with has a dequeue attempt with ticket that took its ticket before .
Proof. The tail counter only grows, except at line L15, which sets it to the head counter when that is larger. The tickets handed out so far are below the current counter , so . The head counter was above at , so every ticket below has been taken by a dequeue attempt.
Linearization of a CRQ. Call an enqueue attempt successful if it stores. Define the points:
- A successful enqueue attempt with ticket : = its fetch-and-add at line L1. For a CRQ created at line L18 with its first item, the item has ticket 0 and its point is defined in Part 2.
- A dequeue that returns the item of ticket : , where and are the fetch-and-adds of the two attempts and is the largest successful ticket below ; when several points coincide, enqueues come first, then dequeues in ticket order.
- A dequeue that returns EMPTY: its read of the tail at line L13, at time .
Each point is inside its call: is at or after , and before the return of , since returns only after stored (after ) and, by induction, after (the item of is taken by the attempt with ticket , which got its ticket before ; if has not returned yet, its point is at most the later of its own two fetch-and-adds and the point before it, all of which precede the return of , as each of them is a fetch-and-add that handed out a smaller ticket than or , and so happened earlier).
Theorem 1 (a CRQ is a linearizable queue that may refuse enqueues). In the order of the points, the enqueues are in ticket order, the dequeues that return items are in ticket order, each item is dequeued after it is enqueued, and at every EMPTY point all enqueued items have been dequeued. So the sequence is a legal history of a FIFO queue.
Proof. Enqueue points are fetch-and-adds on the tail, so they are in ticket order. Dequeue points are in ticket order by their definition. , and ties put enqueues first. Since enqueues and dequeues of items are both in ticket order and Lemma 3 pairs each item with the dequeue of its own ticket, the -th dequeue returns the -th enqueued item: before all successful enqueues with tickets below are linearized (for , the fetch-and-add is before , since the tail hands out tickets in increasing order), and those with tickets above that are linearized before come after the item of in the queue. So removes the front.
For an EMPTY at with ticket : by Lemma 5, each enqueue linearized before has ticket . Its ticket is not : the attempt with ticket cannot store by Lemma 3(b). For , the dequeue attempt with ticket took its ticket before , and by Lemma 3(a) it returns the item; its point is before by induction on , since both fetch-and-adds were before . So every item enqueued before is dequeued before , and the queue is empty at . If ties with a dequeue point, that point is a fetch-and-add, which is a different step; there are no ties. Pending calls: a pending enqueue that stored is kept, and a pending dequeue whose ticket matches a stored item is completed with that item (Lemma 3(a)); the others are removed.
Part 2: the list of CRQs
Lemma 6 (the list). (a) changes once, from NIL, by line L18, and only after is closed. (b) and move only to the next CRQ in the list. (c) moves from only after some dequeue saw NIL and then had an attempt on return EMPTY.
Proof. (a) Line L18 runs only after CRQ-ENQUEUE returned CLOSED, which happens after the bit was set, by the caller or another thread (lines L2 and L6). (b) and (c) by lines L16 and L22.
The abstract queue. Let be the concatenation, in list order from to the end, of the CRQ queues of Theorem 1. For a CRQ created at line L18 with item , linearize the enqueue of at the CAS of that links ; this is inside the call.
Theorem 2 (linearizability). The LCRQ is linearizable with respect to the sequential FIFO queue.
Proof. Use the points of Theorem 1 for each CRQ, and the CAS of for first items. Every enqueue call is linearized once: its last attempt stored, or its CAS at line L18 linked its CRQ; earlier attempts returned CLOSED and did nothing (Lemma 4). Every dequeue call that returns an item is linearized at the point of its last attempt.
Items enter at the end: an enqueue that stores into does so while is open, so while is NIL by Lemma 6(a), and is then the last CRQ. A new CRQ is appended only after is closed, so no item enters afterwards. So the order of enqueue points is the order of items in .
Items leave at the front: a dequeue attempt on returns an item of , the front of by Theorem 1. All CRQs before in are empty at that point: moved past each of them only after a drain (Lemma 6(c)) that returned EMPTY after their was set, so after they were closed, and a closed CRQ that was empty at a point stays empty (no more items enter it by Lemma 4). So the item is the front of .
An LCRQ EMPTY is returned at line L20 after an attempt on returned EMPTY at its point , and was NIL at a later read. At , is empty, CRQs before are empty as above, and there is no CRQ after , since was still NIL later. So is empty at . Since all points are inside their calls, real time is respected.
Part 3: progress
Theorem 3 (lock-freedom). In every infinite execution in which some operation is pending, infinitely many operations finish.
Proof. Suppose that from some moment on no operation finishes. Each attempt of CRQ-ENQUEUE or CRQ-DEQUEUE finishes in a bounded number of its own steps, except for the retries of the inner loop of lines L8 to L12 and of FIX-STATE. A retry of lines L8 to L12 happens only after a CAS2 failed, so after another CAS2 on the same cell succeeded, and by Lemma 1 a cell has at most two changes per round before its round grows; a round grows only through a dequeue ticket of that round, and tickets are only handed out by fetch-and-adds, each a step of an attempt. FIX-STATE retries only if the tail changed, which also needs another step to succeed. So infinitely many attempts are made.
Enqueue side: each CRQ-ENQUEUE call returns CLOSED after at most failed tries (line L6), so each call makes finitely many tries per CRQ. If a call gets CLOSED, it either links a new CRQ and finishes, or its CAS of line L18 fails because another call linked one, and that call then finishes after one more step, or moves on and it tries the new CRQ. So infinitely many enqueue tries without a finished enqueue are impossible: with finitely many threads, some enqueue would close a CRQ and link a new one, and finish.
Dequeue side: a dequeue attempt that does not return an item finds at line L13 and retries; this means an enqueue attempt took a ticket above . Infinitely many such retries need infinitely many enqueue tickets, which the enqueue side excludes from some moment on (no enqueue tries without finished enqueues means no tickets). After that, the next attempt of a dequeue sees and returns EMPTY, or returns an item. At line L20, a dequeue either returns EMPTY or moves to the next CRQ; there are finitely many CRQs, since no new one is linked, so it finishes. Both contradict the assumption.