Proofs: Ctrie (concurrent hash-array mapped trie)
These are the human proofs of Ctrie (concurrent hash-array mapped trie), at full length.
The Ctrie is a linearizable, lock-free map full
This proof shows: correctness, linearizability, progress.
We use the pseudocode of the page, without snapshots. A READ and a CAS of the word of an I-node are atomic, and CAS compares by identity. All other nodes are immutable once created. For a hash , write for the group ; a node is at depth if it is reached from the root through C-node branches.
Reachability. An I-node, C-node, S-node, T-node or L-node is reachable if it can be reached from the root: from an I-node to its , from a C-node to its branches, from a T-node to its S-node, from an L-node to its S-nodes. The path of a reachable node is the sequence of groups of the C-node branches on the way.
The abstract map. is the set of pairs (key, value) of the reachable S-nodes.
Lemma 1 (final tombs). Once the of an I-node is a T-node, it never changes again.
Proof. Every CAS on a expects the C-node or the L-node that was read before (lines L10, L13, L15, L21, L25, L26, L28), never a T-node.
Lemma 2 (shape). At every moment: (a) each reachable I-node has exactly one path, and so does each reachable S-node; (b) a reachable S-node of key whose path has length has a path equal to , and an S-node in an L-node has all bits of its hash equal to the bits of the path; (c) no key has two reachable S-nodes; (d) the root's is a C-node; (e) an I-node stops being reachable only at a successful CAS of line L26 or L28 that replaces it by the S-node of its T-node, and an I-node that is reachable and whose is not a T-node stays reachable with the same path until its becomes a T-node.
Proof. By induction on the successful CAS steps, which are the only steps that change any reachable node. Initially the root holds an empty C-node. Consider a successful CAS on from to . By (e) for the moment of the CAS, is reachable with a fixed path of length : either it is the root, or its is , which is not a T-node, and every CAS on it before kept it in place.
- Line L10 adds the S-node at the group of , which was empty in . The thread reached along the groups of (it chose each branch by FLAGPOS), so (b) holds for . Since the group was empty, no S-node of the key was under , and by (b) no S-node of the key exists elsewhere; so (c) holds.
- Line L12 and L13: if , the S-node of the key is replaced at the same place. Otherwise DUAL builds a fresh subtree with and , whose hashes agree on the groups below , splitting them at the first group where they differ, or in an L-node if they agree on all bits. Fresh I-nodes become reachable with one path each. (b) and (c) hold.
- Line L15 replaces the pair of the key in an L-node, or adds it, and line L25 removes it; both keep (b) and (c).
- Line L21 removes the S-node of the key, and CONTRACT may turn a C-node with one S-node into a T-node of it: the S-node keeps its path, since a T-node is entered without a new group. Not at the root, since is required, so (d) holds.
- Line L26 replaces each branch I-node of whose node was a T-node at the moment COMPRESS read it by that T-node's S-node . By Lemma 1, is still that T-node at the CAS. The S-node moves from path $P, g, $ (inside the T-node) to path (directly in the branch ), which is the same path, so (b) holds; stops being reachable, while its is a T-node, which is (e). CONTRACT then acts as above.
- Line L28 does the same for the single I-node , after it read a T-node in , which stays (Lemma 1).
No other step removes a branch that is an I-node, so (e) holds.
Lemma 3 (the abstract map changes only at update points). A successful CAS of line L10, L13 or L15 sets the pair of to in and changes nothing else. A successful CAS of line L21 or L25 removes the pair of from , and the pair removed is the one returned. Every other successful CAS (lines L26 and L28) leaves unchanged.
Proof. The CAS changes only below , which is reachable by Lemma 2(e). The cases are those of the proof of Lemma 2: lines L10, L13, L15 add or replace the S-node of the key; line L21 removes , whose key is , and returns , with CONTRACT only regrouping the remaining S-node; line L25 removes the pair of from the L-node, whose value is returned. At lines L26 and L28, each S-node that moves is still reachable afterwards, and nothing else changes.
Lemma 4 (reads). Let an operation on key read from at line L2 or L17, at depth , and let be a C-node or an L-node. At the moment of the read, is reachable with path .
Proof. The root is reachable with the empty path. The operation reached through branches chosen by FLAGPOS on , from C-nodes it read in their I-nodes. By induction on , its parent was reachable with the right path when the thread read the C-node of , and was the branch of group of . So was reachable just then. Afterwards, stays reachable with the same path until its becomes a T-node (Lemma 2(e)). At the later read, is not a T-node, and by Lemma 1 it was not a T-node at any moment in between. So is still reachable with the same path.
Theorem 1 (linearizability). Each history of the Ctrie is linearizable with respect to the sequential map.
Proof. Choose the points:
- insert: its successful CAS at line L10, L13 or L15;
- remove that returns a value: its successful CAS at line L21 or L25;
- lookup, and remove that returns NOTFOUND: the last READ of an I-node at line L2 or L17 in the last round, the one that did not restart.
Each point is inside its call; the order of the points respects real time. By Lemma 3 the updates change exactly as the sequential operations do at their points, with the right results.
A lookup or a remove that returns NOTFOUND or a value without a CAS read from at depth , with reachable on the path of by Lemma 4. By Lemma 2(b) and (c), the S-node of , if any, is under , in the branch of group of , or in if is an L-node. The node is immutable. If the group is empty, at that moment. If the branch is an S-node, it decides: its value, if its key is , or NOTFOUND, since can be nowhere else. If the branch is an I-node, the read was not the last one. In an L-node, the value of in it decides. So the result is , or NOTFOUND, at the point. Rounds that restart do not change except through CLEAN, which changes nothing by Lemma 3.
Theorem 2 (lock-freedom). In every infinite execution in which some operation is pending, infinitely many operations finish.
Proof. Suppose that from some moment no operation finishes. Then after no CAS of an update point succeeds, since a call whose update CAS succeeds ends after its line L22 and CLEAN-PARENT, and CLEAN-PARENT goes back to line L27 only after a failed CAS on , so only if some other CAS on succeeded. So after the only successful CASes are those of lines L26 and L28.
Count, for the reachable part of the trie, the potential = the number of reachable I-nodes. Each successful CAS of line L28 removes one reachable I-node, and one of line L26 removes at least as many as it resurrects; a CAS of line L26 that resurrects nothing and changes nothing else replaces a C-node by an equal copy. After no CAS adds a reachable I-node, since only line L13 creates them. So the successful CASes of lines L26 and L28 that remove an I-node are finitely many. A thread does a CAS at line L26 only after it found a T-node in an I-node below ; if the CAS removes nothing, was not in , so had already been detached, and after its restart from the root the thread cannot meet again (Lemma 2(e)). There are finitely many detached I-nodes with T-nodes (each was reachable before or has been detached since, and none is created). So after some moment no CAS succeeds at all, and every reachable I-node has a fixed . From then on, a thread that restarts goes down a trie that does not change; it does not meet a T-node, since any reachable T-node would be met by a cleaning CAS that succeeds, contradicting ; so its next CAS, on the current node of a reachable I-node, succeeds, or it returns without one. Both contradict the assumption.