Proving linearizability & lock-freedom Viktor Vafeiadis - - PowerPoint PPT Presentation
Proving linearizability & lock-freedom Viktor Vafeiadis - - PowerPoint PPT Presentation
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 ==
Michael & Scott non-blocking queue
null
X 1 3 2 head tail
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
null
X 1 3 2 head tail
Michael & Scott non-blocking queue
null
X 1 3 2 head tail 4
null
Michael & Scott non-blocking queue
X 1 3 2 head tail 4
null
Michael & Scott non-blocking queue
X 1 3 2 head tail 4
null
Michael & Scott non-blocking queue
X 1 3 2 head tail 4
null
Enqueue & dequeue
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) { if (CAS(&t→next,n,m)) break; } else { CAS(&Q→tail,t,n); } } CAS(&Q→tail,t,n); }
dequeue () { while (true) { h = Q→head; t = Q→tail; n = h→next; if (Q→tail ≠ t) continue; if (h == t) { if (n == null) return EMPTY; CAS(&Q→tail,t,n); } else { if (CAS(&Q→head,h,n)) return n→val; } } }
Don’t read the code!
RGSep actions (pre-/postcondition pairs)
- Summarize the shared state updates
A B
null
head tail A B head tail A B head tail A
null
head tail A B head tail A B head tail Enqueue Dequeue Advance tail pointer
The actions of enqueue & dequeue
enqueue(v) { m = new Node(); m→val = v; m→next = null; while (true) { t = Q→tail; n = tail→next; if (Q→tail ≠ t) continue; if (n == null) { if (CAS(&t→next,n,m)) break; } else { CAS(&Q→tail,t,n); } } CAS(&Q→tail,t,n); }
dequeue () { while (true) { h = Q→head; t = Q→tail; n = h→next; if (Q→tail ≠ t) continue; if (h == t) { if (n == null) return EMPTY; CAS(&Q→tail,t,n); } else { if (CAS(&Q→head,h,n)) return n→val; } } }
ENQUEUE DEQUEUE
- ADV. TAIL
- ADV. TAIL
- ADV. TAIL
Local updates
Length (first attempt)
length() { num = 0; curr = Q→head→next; while (curr ≠ null) { num++; curr = curr→next; } return num; }
X 1 3 4
null
2 head tail
length() { num = 0; while (true) { t = Q→tail; n = tail→next; if (n == null) break; CAS(&Q→tail,t,n); } curr = Q→head→next; while (curr ≠ null) { num++; if (curr == t) break; curr = curr→next; } return num; }
Length (second attempt)
Read Q→tail, and ensure that Q→tail→next == null
length() { num = 0; do { h = Q→head; while (true) { t = Q→tail; n = tail→next; if (n == null) break; 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; }
Length (third attempt)
Get a snapshot of Q→head and Q→tail and ensure that Q→tail→next==null.
Verification challenge
Functional correctness (linearizability): Every method executes ‘atomically’ and obeys a high-level specification Liveness properties, e.g. lock-freedom: At all times, some outstanding method call is guaranteed to terminate.
VMCAI ’09
CAV ’10
POPL ’09
Linearizability
Every method executes ‘atomically’ & obeys a functional correctness specification Shared variable: AQ
AQ := append(singleton(v), AQ); return 0; if (AQ == empty) { return EMPTY; } else { r := hd(AQ); AQ := tl(AQ); return r; } dequeue() spec enqueue(v) spec
Linearizability & forward simulation
Linearizability: The implementation (of every method) is a refinement
- f an atomic specification.
Standard proof technique: forward simulation Abstract (spec) Concrete (impl)
Sconc S’conc Sabs S’abs
Linearization points
The implementation is a refinement of an atomic specification.
abstract execution concrete execution
linearization point (LP)
Linearization point of enqueue
- Lin. Point
(provided CAS succeeds)
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) { if (CAS(&t→next,n,m)) break; } 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
LP provided
this test fails, and the h==t test succeeds the n==null test succeeds
Condition:
¬prophecy(Q→tail ≠ t)
∧ h == t ∧ n == null dequeue () { while (true) { h := Q→head; t := Q→tail; n := h→next; if (Q→tail ≠ t) continue; if (h == t) { if (n == null) return EMPTY; CAS(&Q→tail,t,n); } else { if (CAS(&Q→head,h,n)) return n→val; } } }
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 efgectful LPs:
assert(lres==UNDEF); lres := method_spec();
- 3. At the return points, check
res == lres concrete result abstract result
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-efgects) can_return[abs_method()] := true;
- 3. At the return points, check
can_return[res] == true
Validating pure executions
concrete result
For example...
if (AQ == empty) { return EMPTY; } else { r := hd(AQ); AQ := tl(AQ); return r; } if (AQ == empty) { can_return[EMPTY] := true; } dequeue spec pure lin. checker
Auxiliary variables: lres, can_return
- 1. At the entry to the function:
lres := UNDEF; ∀i. can_return[i] := false;
- 2. At the candidate efgectful 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-efgects.
- 4. At the return points, check
(res==lres) ∨ (lres==UNDEF ∧ can_return[res])
Enhanced LP validation
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 3
null
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 3
null
c
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 3
null
c
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 3
null
c
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 3 5
null
c
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 5 3
null
c
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 3 5
null
c
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
LPs in other threads
add (e) { ... } remove (e) { ... } contains (e) { c := H; while (c→val < e) c := c→next; return (c→val == e); }
1 7 3 5
null
c
- INF
contains(5) || add(5); remove(3); remove(5)
+INF
Auxiliary variables: lres, can_return
- 1. At the entry to the function:
lres := UNDEF; ∀i. can_return[i] := false;
- 2. At the candidate efgectful 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])
Enhanced LP validation
CAVE: Concurrent Algorithm VErifier
http://www.mpi-sws.org/~viktor/cave
Implementation
- RGSep action inference
[VMCAI 2010]
- Shape-value abstract domain
[VMCAI 2009]
- Algorithm for proving linearizability
[CAV 2010]
Experiments (linearizability)
Algorithm Lines Ops Efg Pure LpO Time (s) DCAS stack 100 8 4 5 0.3 Treiber stack 100 8 4 5 0.3 M&S two-lock queue 85 4 3 2 16.5 M&S non-blocking queue 127 4 3 2 4.9 DGLM non-blocking queue 126 4 3 2 7.6 Pessimistic set 100 3 2 3 247.8 V&Y DCAS-based set 101 3 2 3 51.0 ORVYY lazy set 94 3 2 3 1 521.5
Lines: lines of code: excluding comments & specs Ops: # methods Efg: # efgectful methods Pure: # methods with a pure execution path LpO: # linearization points in other threads
Non-blocking liveness properties
Proving that non-blocking algorithms don’t block [Gotsman, Cook, Parkinson, Vafeiadis, POPL’09]
Non-blocking liveness properties
- Wait-freedom:
Every thread is guaranteed to complete its operation.
- Lock-freedom:
[Provided that there is at least one thread scheduled,] some thread is guaranteed to complete its operation.
- Obstruction-freedom:
Every thread is guaranteed to complete its operation, provided it eventually executes in isolation. (No assumptions about the scheduler.)
Lock-freedom reduces to termination
A library is lock-free ifg init(); ‖n { if(?) enqueue(?) else if(?) dequeue() else length() } terminates when n(=the number of threads) is bounded.
Reminder: enqueue & dequeue
enqueue(v) { m = new Node(); m→val = v; m→next = null; while (true) { t = Q→tail; n = tail→next; if (Q→tail ≠ t) continue; if (n == null) { if (CAS(&t→next,n,m)) break; } else { CAS(&Q→tail,t,n); } } CAS(&Q→tail,t,n); }
dequeue () { while (true) { h = Q→head; t = Q→tail; n = h→next; if (Q→tail ≠ t) continue; if (h == t) { if (n == null) return EMPTY; CAS(&Q→tail,t,n); } else { if (CAS(&Q→head,h,n)) return n→val; } } }
ENQUEUE DEQUEUE
- ADV. TAIL
- ADV. TAIL
- ADV. TAIL
Guarantee-strengthening
I don’t execute ENQUEUE or DEQUEUE infinitely often I don’t execute ENQUEUE, DEQUEUE, or ADV.TAIL infinitely often I don’t execute ENQUEUE or DEQUEUE infinitely often I don’t execute ENQUEUE, DEQUEUE, or ADV.TAIL infinitely often
I terminate I terminate
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions □(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL ¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
□(ENQUEUE ∨ DEQUEUE ∨ ADV.TAIL)
Guarantee-strengthening
First, do action inference Then, iteratively eliminate actions
¬□◊ENQUEUE
Terminates
¬□◊DEQUEUE ¬□◊ADV.TAIL ¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL
Terminates
¬□◊ENQUEUE ¬□◊DEQUEUE ¬□◊ADV.TAIL