Proofs: B-link tree
These are the human proofs of B-link tree, at full length.
The B-link tree is a linearizable, deadlock-free map with lock-free searches full
This proof shows: correctness, linearizability, progress.
We use the pseudocode of the page. A READ or WRITE of a node is one atomic step on its whole page, and so are the reads and writes of the anchor (the root and the height). LOCK and UNLOCK are those of a mutex. Keys come from a totally ordered set, extended by and .
Writers. Every WRITE of an existing node is done by the holder of 's lock, and the write of a new node at line L14 happens before any pointer to is written. So while a thread holds the lock of , the page it read after locking is the current page of , and it stays current until this thread writes it.
Low bounds. Give each node a fixed low bound: for the first leaf and for each new root, and for the node created by a split at line L14, where is the new high key of the node that split. A node is in the chain of its level once a pointer to it has been written.
Lemma 1 (the chain invariant). At every moment, for each level up to the height:
- The nodes of level in the chain, from the leftmost node of the level along the right links, are with , and , so their ranges partition the keys.
- Every key of a page lies in the range of its node (for an internal node, every separator except the last lies inside the range, and the last equals the high key).
- For an internal node with entries : , and for ; each is a node of the level below in the chain.
Proof. We prove Lemma 1 and Lemma 2 together, by induction on the steps of the execution: Lemma 2 for walks up to a step uses Lemma 1 for the states before it, and Lemma 1 after a write uses Lemma 2 for the walk of the writer. Initially there is one leaf with range . We check every write.
A write at line L9, L13 or L20 of a node changes its keys but not its high key or link. At line L13 the inserted key is in the range of : the thread reached with (Lemma 2 below) and moved right until . At an internal node, the entry with the smallest becomes ; part 3 still holds for , and is the new separator before . So 2 and 3 hold. Deletions only remove keys.
The write of at line L14 changes no node in the chain. The write of at line L14 replaces the page , with range and link , by with high key and link , while holds with high key and link . So in the chain, is replaced by with ranges and , and part 1 holds. The keys of that are at most are in , and the others in , so part 2 holds. If is internal, starts with an entry whose child has low bound equal to the separator before it in , which is , so part 3 holds for , and it is unchanged for .
A new root with entries and is made only if is the root, so is the leftmost and, before its split, only node of its level: then the level is , and the new root satisfies 1 to 3 with range . The height grows by one at the same step.
Lemma 2 (routing). Suppose a thread at level reads node while looking for key with . If in the page read, the link satisfies . If and is internal, the chosen child satisfies . So every node that SEARCH, the descents of lines L7, L16 and L19, and MOVE-RIGHT visit has , and the last node read covers at the moment of its read.
Proof. The first case is part 1 of Lemma 1. In the second, , and by part 3, or has . Every walk starts at the root, of low bound . A walk stops at level only when in the page just read, and with the node covers at that read. Since low bounds never change, stays true after the read. For the move right at the parent with key in line L17: the popped node was visited at the level above on the way to the leaf, so ; the leaf that split had the low bound of the node reached from , which is at least , and with nonempty and inside , so . The same holds by induction at each higher level.
The abstract map. At each moment, the map is the set of pairs in the pages of the leaves in the chain. By part 1 and 2 of Lemma 1, each key is in at most one leaf, the one whose range holds it.
Theorem (linearizability). Each history of the B-link tree is linearizable with respect to the sequential ordered map.
Proof. Choose the points:
- a search: its last READ, at line L2 or L5;
- an insert that finds present: its WRITE at line L9; an insert of a new key: its WRITE of the leaf, at line L13 or at the second WRITE of line L14;
- a delete that finds : its WRITE at line L20; a delete that does not: the READ of the locked leaf after MOVE-RIGHT.
Each point is inside its call, so the order of the points respects real time; pending calls past their point are completed, the others removed.
Search: by Lemma 2 its last page read is of the leaf that covers at that moment, so the value it returns is then, or NIL.
Updates: by Lemma 2 the leaf locked after MOVE-RIGHT covers , and since the thread holds the lock, its page is the current page and keeps covering until the thread writes it (only a split of , by a lock holder, could shrink its range). An insert that finds changes only the value of in and returns the old value. An insert of a new key at line L13 adds to the leaf that covers . At line L14, the first WRITE, of , changes no leaf in the chain, so it does not change ; the second puts and in the chain, whose pairs are the old pairs of plus . So gains exactly , and the insert returns NIL as it should. All later steps of the insert act on internal nodes or the anchor and do not change . A delete removes from the leaf that covers it and returns its value; a delete that does not find in the current page of the covering leaf returns NIL with undefined at that moment. Every write of a leaf page is one of these points, so changes only at linearization points, each by the change of the sequential map.
Theorem (progress). Searches take no locks and never wait. Updates are deadlock-free: if threads keep taking steps, some update that has started finishes, and with finitely many updates every operation finishes.
Proof. Order the nodes by (level, low bound). This order is fixed, since neither changes, and it is total among nodes of the chain, since low bounds on a level are distinct by part 1 of Lemma 1. An update that holds locks only waits for a lock that is higher in the order: at line L18 it holds and waits for the link, of a larger low bound on the same level; at line L17 it holds and waits for a node one level up. So a cycle of threads waiting for locks is impossible. The anchor is held only inside one atomic step, so nobody waits for it. The only other wait is the loop of line L16 when the top level has a node that is not the root: then the root has split, its new right part is in the chain, and the thread that split holds the lock of until it makes the new root, which it does at its next step with the anchor without waiting for anything. So every waiting thread waits for a thread that is not waiting, or for one that waits for a higher lock, and the highest one in a chain of waits makes progress.
Each update does at most one split per level, and the number of levels grows only by root splits, so an update that keeps getting locks finishes after a bounded number of its own steps plus the moves right. A walk at level moves right only past nodes that exist in the chain between its node and the one that covers ; each move right passes a node created by a split, and with finitely many updates there are finitely many splits, so every walk ends.