Using abstract interpretation to correct synchronization faults
Eric Koskinen
Yale University
VMCAI · 17 January 2017 Pietro Ferrara
JuliaSoft
Omer Tripp
Peng Liu
IBM Research
Using abstract interpretation to correct synchronization faults - - PowerPoint PPT Presentation
Using abstract interpretation to correct synchronization faults Pietro Ferrara Omer Tripp Peng Liu Eric Koskinen JuliaSoft Google IBM Research Yale University VMCAI 17 January 2017 Concurrency is here to stay. Thread 1 Thread 2
Eric Koskinen
Yale University
VMCAI · 17 January 2017 Pietro Ferrara
JuliaSoft
Omer Tripp
Peng Liu
IBM Research
Queue HashMap List
Thread 1 Thread n Thread 2
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t1: replace(11:00) / δ t1: return δ t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2
Not serializable! t1 goes first But returns δ ? t2 goes second Returns β ? Now: 11:00 —> α
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2
Slot Res 10:00 — 11:00 α 12:00 γ 13:00 — Slot Res 10:00 — 11:00 δ 12:00 γ 13:00 —
Acceptable Final States: t1 first, returns β t2 second, returns α t2 first, returns β t1 second, returns δ
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2 Existing approaches to enforcing serializability . . .
t2: lock(13:00);
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2
t1 t1
t1: remove(10:00) / α t1: if (…) t1: replace(11:00) / β t1: return β t1: lock(10:00); lock(11:00)
t2
t2: lock(13:00); lock(11:00)
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2
t1 t1
t1: remove(10:00) / α t1: if (…) t1: replace(11:00) / β t1: return β t1: lock(10:00); lock(11:00)
t2
get$ if(…)$ new$ putIfAbsent$ return$ get$ if(…)$ new$ putIfAbsent$ return$
pessimis,c%
t1% t2%
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
remove if replace remove new replace
t1: remove(10:00) / α t1: if (…) t1: replace(11:00) / β t1: return β t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t1: replace(11:00) / β t1: return β t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
conflict.
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
get$ if(…)$ new$ putIfAbsent$ return$ get$ if(…)$ new$ putIfAbsent$ return$
pessimis,c%
t1% t2%
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
remove if replace remove new replace
get$ if(…)$ new$ putIfAbsent$ return$ get$ if(…)$ new$ putIfAbsent$
retry%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace remove new replace
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Other options? Perform a correction!
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t1: replace(11:00) / δ t1: return δ t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2
Not serializable!
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
Slot Res 10:00 α 11:00 β 12:00 γ 13:00 —
t1 t2
t1: replace(11:00) / δ t1: return δ
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 — 11:00 β 12:00 γ 13:00 —
♥
β β α α δ
Serializable Target
Slot Res 10:00 — 11:00
δ
12:00 γ 13:00 —
t1 first, returns β t2 second, returns α
t1: replace(11:00) / δ t1: return δ
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 — 11:00 β 12:00 γ 13:00 —
♥
α
Serializable Target
Slot Res 10:00 — 11:00 α 12:00 γ 13:00 —
t2 first, returns β t1 second, returns δ
get$ if(…)$ new$ putIfAbsent$ return$ get$ if(…)$ new$ putIfAbsent$ return$
pessimis,c%
t1% t2%
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
remove if replace remove new replace
get$ if(…)$ new$ putIfAbsent$ return$ get$ if(…)$ new$ putIfAbsent$
retry%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace remove new replace
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Yes, we have proved so.
states? Via a static abstract interpretation.
to a correct post-state? Via a dynamic runtime system.
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Transition System
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Remember where we started
t
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Act sort of optimistically: march ahead w local changes
,opt)
L t
'
Remember the operation that was performed
,Lt•opt)], σ, L)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Replay the mutations on the shared state Parameterize by property of interest (Serializability) L t
,opt) ,Lt•opt)], σ, L)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
L t
,opt) ,Lt•opt)], σ, L)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Now let’s add correction . . .
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Γ,(t,s) ⊢ s ⟶* (T,μ’,σ,L) Γ,(t,s) ⊢ (T,μ,σ,L) ⟶ (T,μ[t ↦ μ’(t)],σ,L) corr t
Recall reference state s for txn t Txn t could have gone to some other μ’ Pretend that it did exactly that.
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Let’s see these rules applied to the example . . .
t1: replace(11:00) / δ t1: return δ
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 — 11:00 β 12:00 γ 13:00 —
♥
β
Serializable Target
Slot Res 10:00 — 11:00
δ
12:00 γ 13:00 —
t1 first, returns β t2 second, returns α
Rules
β
t1: replace(11:00) / δ t1: return δ
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 — 11:00 β 12:00 γ 13:00 —
♥
α
Serializable Target
Slot Res 10:00 — 11:00
δ
12:00 γ 13:00 —
t1 first, returns β t2 second, returns α
Rules
β β
t1: replace(11:00) / β t1: return β
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 — 11:00 α 12:00 γ 13:00 —
♥
Serializable Target
Slot Res 10:00 — 11:00
δ
12:00 γ 13:00 —
t1 first, returns β t2 second, returns α
Rules
to shared state
t1: replace(11:00) / δ t1: return δ
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / β t2: return β
Slot Res 10:00 — 11:00 α 12:00 γ 13:00 —
♥
β α α
Serializable Target
Slot Res 10:00 — 11:00
δ
12:00 γ 13:00 —
t1 first, returns β t2 second, returns α
Rules
β δ
t1: replace(11:00) / δ t1: return δ
updateReservation(Slot old_slot, Slot new_slot) : Reservation = { var r = map.remove(old_slot) // remove if exists if (r == null) { r = new Res() } val clobbered = map.replace(new_slot, r) return clobbered; }
t1: remove(10:00) / α t1: if (…) t2: remove(13:00) / null t2: new Res() / δ t2: replace(11:00) / α t2: return α
Slot Res 10:00 — 11:00 δ 12:00 γ 13:00 —
♥
β
Serializable Target
Slot Res 10:00 — 11:00
δ
12:00 γ 13:00 —
t1 first, returns β t2 second, returns α
Rules
β
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
pessimism is like an almost trivial restriction in which conflicting executions never occur; no need to correct
the initial state (abort)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
intermedia (or final) states (fixpoint over an inter-procedural cross-product CFG)
correction)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
If we started from a pre-state where k was not in the map and the argument was v, then in the post-state k is made to point to the value v pointed-to by the argument k in the pre-state.
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
(what operations to perform)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
simultaneously (e.g. loop parallelization)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
JSON)
apps FxValueRendererFactory (render transparently certain objects in a language)
(generic POF serialization via reflection)
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
w.r.t. lock-based synchronization.
that is twice the one obtained by STM.
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Perf vs Workload Perf vs # of threads
get$ if(…)$ new$ putIfAbsent$ return$
correct%
correc,ve%
get$ if(…)$ new$ putIfAbsent$ return$ t1% t2%
remove if replace remove new replace
Thank you! eric.koskinen@yale.edu