Proving linearizability & lock-freedom Viktor Vafeiadis MPI-SWS
Michael & Scott non-blocking queue head tail null X 1 2 3
CAS – compare & swap CAS (address, expectedValue, newValue) { atomic { if ( *address == expectedValue ) { *address = newValue; return true; } else { return false; } } } -A single hardware instruction on x86
Michael & Scott non-blocking queue head tail null X 1 2 3
Michael & Scott non-blocking queue null head tail 4 null X 1 2 3
Michael & Scott non-blocking queue null head tail 4 X 1 2 3
Michael & Scott non-blocking queue null head tail 4 X 1 2 3
Michael & Scott non-blocking queue null head tail 4 X 1 2 3
Enqueue & dequeue Don’t read the code ! dequeue () { enqueue(v) { while (true) { m = new Node(); m → val = v; h = Q → head; m → next = null; t = Q → tail; while (true) { n = h → next; t = Q → tail; if (Q → tail ≠ t) continue ; n = tail → next; if (h == t) { if (Q → tail ≠ tail) continue ; if (n == null) if (n == null) { return EMPTY; if (CAS(&t → next,n,m)) CAS(&Q → tail,t,n); break; } else { } else { if (CAS(&Q → head,h,n)) CAS(&Q → tail,t,n); return n → val; } } } } CAS(&Q → tail,t,n); } }
RGSep actions (pre-/postcondition pairs) Summarize the shared state updates head tail head tail Enqueue null null A B A head tail head tail Dequeue A B A B head tail head tail Advance tail pointer A B A B
The actions of enqueue & dequeue enqueue(v) { dequeue () { m = new Node(); while (true) { m → val = v; Local h = Q → head; m → next = null; updates t = Q → tail; while (true) { n = h → next; t = Q → tail; if (Q → tail ≠ t) continue ; n = tail → next; if (h == t) { if (Q → tail ≠ t) continue ; if (n == null) if (n == null) { return EMPTY; if (CAS(&t → next,n,m)) ENQUEUE CAS(&Q → tail,t,n); ADV. TAIL break; } else { } else { if (CAS(&Q → head,h,n)) DEQUEUE CAS(&Q → tail,t,n); ADV. TAIL return n → val; } } } } } CAS(&Q → tail,t,n); ADV. TAIL }
Length (first attempt) length() { num = 0; curr = Q → head → next; while (curr ≠ null) { num++; curr = curr → next; } return num; } head tail null X 1 2 3 4
Length (second attempt) length() { num = 0; while (true) { t = Q → tail; n = tail → next; Read Q → tail, and ensure if (n == null) break ; that Q → tail → next == null CAS(&Q → tail,t,n); } curr = Q → head → next; while (curr ≠ null) { num++; if (curr == t) break; curr = curr → next; } return num; }
Length (third attempt) length() { num = 0; do { h = Q → head; while (true) { Get a snapshot of t = Q → tail; Q → head and Q → tail n = tail → next; and ensure that if (n == null) break ; Q → tail → next==null. CAS(&Q → tail,t,n); } } while (h ≠ Q → head); curr = h → next; while (curr ≠ null) { num++; if (curr == t) break; curr = curr → next; } return num; }
Verification challenge Functional correctness (linearizability): Every method executes ‘atomically’ and obeys a high-level specification VMCAI ’09 CAV ’10 Liveness properties, e.g. lock-freedom: At all times, some outstanding method call is guaranteed to terminate. POPL ’09
Linearizability Every method executes ‘atomically’ & obeys a functional correctness specification Shared variable: AQ enqueue(v) spec AQ := append(singleton(v), AQ); return 0; dequeue() spec if (AQ == empty) { return EMPTY; } else { r := hd(AQ); AQ := tl(AQ); return r; }
Linearizability & forward simulation Linearizability: The implementation (of every method) is a refinement of an atomic specification. Standard proof technique: forward simulation Abstract (spec) S abs S’ abs Concrete (impl) S conc S’ conc
Linearization points The implementation is a refinement of an atomic specification. abstract execution concrete execution linearization point (LP)
Linearization point of enqueue enqueue(v) { m := new Node(); m → val := v; m → next := null; while (true) { t := Q → tail; n := tail → next; if (Q → tail ≠ tail) continue ; if (n == null) { Lin. Point if (CAS(&t → next,n,m)) break; (provided CAS succeeds) } else { CAS(&Q → tail,t,n); } } CAS(&Q → tail,t,n); }
Proof search for the LP ? For each execution path of each method, choose a candidate LP Check whether it is a valid LP Does this work ?
Proof search for the LP ? For each execution path of each method, choose a candidate LP Check whether it is a valid LP Does this work ? Not quite. 1. LPs can be conditional 2. LPs can be in the code of another thread
LP of dequeue, when it returns EMPTY dequeue () { while (true) { h := Q → head; t := Q → tail; n := h → next; LP provided if (Q → tail ≠ t) this test fails, and continue ; the h==t test succeeds if (h == t) { the n==null test succeeds if (n == null) return EMPTY; CAS(&Q → tail,t,n); } else { if (CAS(&Q → head,h,n)) Condition: return n → val; } ¬prophecy(Q → tail ≠ t) } ∧ h == t } ∧ n == null
Key observation Method executions that logically modify the state have a simple LP. Method executions that do not logically modify the state often have a complex LP. So: Treat these two cases differently. Search for LPs of executions that logically modify the state; Do a non-constructive proof for executions that do not logically modify the state.
Basic LP validation Auxiliary variable: lres 1. At the entry to the function: lres := UNDEF; 2. At the candidate e fg ectful LPs: assert( lres ==UNDEF); lres := method_spec(); 3. At the return points, check res == lres concrete abstract result result
Validating pure executions Auxiliary variable: can_return 1. At the entry to the function: ∀ i. can_return [i] := false; 2. At every point, add a pure ‘LP checker’: if (abs_method() has no side-e fg ects) can_return [abs_method()] := true; 3. At the return points, check can_return [res] == true concrete result
For example... dequeue spec if (AQ == empty) { return EMPTY; } else { r := hd(AQ); AQ := tl(AQ); return r; } pure lin. checker if (AQ == empty) { can_return [EMPTY] := true; }
Enhanced LP validation Auxiliary variables: lres , can_return 1. At the entry to the function: lres := UNDEF; ∀ i. can_return [i] := false; 2. At the candidate e fg ectful LPs: assert( lres= =UNDEF); lres := abs_method() 3. Add pure checkers at every program point: assign can_return [i] := true if method_spec returns i & has no side-e fg ects. 4. At the return points, check (res== lres ) ∨ ( lres ==UNDEF ∧ can_return [res])
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } null -INF 1 3 7 +INF contains(5) || add(5); remove(3); remove(5)
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } null -INF 1 3 7 +INF c contains(5) || add(5); remove(3); remove(5)
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } null -INF 1 3 7 +INF c contains(5) || add(5); remove(3); remove(5)
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } null -INF 1 3 7 +INF c contains(5) || add(5); remove(3); remove(5)
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } null -INF 1 3 5 7 +INF c contains(5) || add(5); remove(3); remove(5)
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } 3 null -INF 1 5 7 +INF c contains(5) || add(5); remove(3); remove(5)
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } 5 3 null -INF 1 7 +INF c contains(5) || add(5); remove(3); remove(5)
LPs in other threads add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c → val < e) c := c → next; return (c → val == e); } 5 3 null -INF 1 7 +INF c contains(5) || add(5); remove(3); remove(5)
Enhanced LP validation Auxiliary variables: lres , can_return 1. At the entry to the function: lres := UNDEF; ∀ i. can_return [i] := false; 2. At the candidate e fg ectful LPs: assert( lres= =UNDEF); lres := abs_method() 3. Add pure checkers at every program point (incl. program points in other threads) 4. At the return points, check (res== lres ) ∨ ( lres ==UNDEF ∧ can_return [res])
Implementation CAVE: Concurrent Algorithm VErifier - RGSep action inference [VMCAI 2010] - Shape-value abstract domain [VMCAI 2009] - Algorithm for proving linearizability [CAV 2010] http://www.mpi-sws.org/~viktor/cave
Recommend
More recommend