Synchronizing the Asynchronous Bernhard Kragl IST Austria Shaz Qadeer Thomas A. Henzinger Microsoft IST Austria
Concurrency is Ubiquitous
Asynchronous Concurrency is Ubiquitous
Asynchronous programs are hard to specify assert Pre(Q) assert Pre(Q) call Q async Q assume Post(Q) assume Post(Q) Asynchronous programs are hard to verify
Structured program vs. Transition relation Init: ๐๐ = ๐๐ 1 = ๐๐ 2 = ๐ a: x := 0 b: acquire(l) acquire(l) Next: โฒ = ๐๐ 2 โฒ = ๐ โง ๐ฆ โฒ = 0 โง ๐๐ ๐, ๐ข 1 , ๐ข 2 ๐๐ = ๐ โง ๐๐ โฒ = ๐๐ 1 c: t1 := x t2 := x โฒ = ๐ โง ยฌ๐ โง ๐ โฒ โง ๐๐ ๐๐, ๐๐ 2 , ๐ฆ, ๐ข 1 , ๐ข 2 ๐๐ 1 = ๐ โง ๐๐ 1 d: t1 := t1+1 t2 := t2+1 โฒ = ๐ โง ๐ข 1 โฒ = ๐ฆ โง ๐๐ ๐๐, ๐๐ 2 , ๐, ๐ฆ, ๐ข 2 ๐๐ 1 = ๐ โง ๐๐ 1 e: x := t1 x := t2 โฒ = ๐ โง ๐ข 1 โฒ = ๐ข 1 + 1 โง ๐๐ ๐๐, ๐๐ 2 , ๐, ๐ฆ, ๐ข 2 ๐๐ 1 = ๐ โง ๐๐ 1 f: release(l) release(l) โฒ = ๐ โง ๐ฆ โฒ = ๐ข 1 โง ๐๐ ๐๐, ๐๐ 2 , ๐, ๐ข 1 , ๐ข 2 ๐๐ 1 = ๐ โง ๐๐ 1 โฒ = ๐ โง ยฌ๐ โฒ โง ๐๐(๐๐, ๐๐ 2 , ๐ฆ, ๐ข 1 , ๐ข 2 ) ๐๐ 1 = ๐ โง ๐๐ 1 g: assert x = 2 โฒ = ๐ โง ยฌ๐ โง ๐ โฒ โง ๐๐ ๐๐, ๐๐ 1 , ๐ฆ, ๐ข 1 , ๐ข 2 ๐๐ 2 = ๐ โง ๐๐ 2 โฒ = ๐ โง ๐ข 2 โฒ = ๐ฆ โง ๐๐ ๐๐, ๐๐ 1 , ๐, ๐ฆ, ๐ข 1 ๐๐ 2 = ๐ โง ๐๐ 2 โฒ = ๐ โง ๐ข 2 โฒ = ๐ข 2 + 1 โง ๐๐ ๐๐, ๐๐ 1 , ๐, ๐ฆ, ๐ข 1 ๐๐ 2 = ๐ โง ๐๐ 2 โฒ = ๐ โง ๐ฆ โฒ = ๐ข 2 โง ๐๐ ๐๐, ๐๐ 1 , ๐, ๐ข 1 , ๐ข 2 ๐๐ 2 = ๐ โง ๐๐ 2 โฒ = ๐ โง ยฌ๐ โฒ โง ๐๐(๐๐, ๐๐ 1 , ๐ฆ, ๐ข 1 , ๐ข 2 ) ๐๐ 2 = ๐ โง ๐๐ 2 Procedures and dynamic thread creation ๐๐ 1 = ๐๐ 2 = ๐ โง ๐๐โฒ = ๐ โง ๐๐(๐๐ 1 , ๐๐ 2 , ๐, ๐ฆ, ๐ข 1 , ๐ข 2 ) complicate transition relation further! Safe: ๐๐ = ๐ โ ๐ฆ = 2
Shared State in Message-Passing Programs P / P# Language โข Windows 8 USB 3.0 driver โข Azure cloud services โข DRONA framework Problem: Monolithic proofs do not scale Question: How can structured proofs help?
Idea: โ Inlining of Event Handlers โ Dispatcher โป Dispatcher โป Event Handlers Event Handlers H1: H1: โฆ โฆ H2: REQ : send REQ C1;C2;C3; H3: H3: โฆ โฆ
Idea: โ Inlining of Event Handlers โ Dispatcher โป Dispatcher โป Event Handlers Event Handlers H1: H1: โฆ โฆ H2: REQ : send REQ C1;C2;C3; C1;C2;C3; H3: H3: โฆ โฆ
Our Contributions Synchronization proof rule Syntax-driven and structured proofs ๐ 1 โผ ๐ 2 โผ โฏ โผ ๐ ๐โ1 โผ ๐ ๐ ๐ is safe ๐ ๐ 1 is safe
Reduction Theorem Sequence of (right)*(none)?(left)* is atomic. Left/right movers Commutativity (right) (both) (both) (left) lock A t := x B x := t+1 C unlock ๐ 1 ๐ 2 ๐ 3 ๐ 4 ๐ 5 ๐ 6 ๐ 7 ๐ 8 A lock t := x x := t+1 unlock B C ๐ 1 ๐ 2 โฒ ๐ 3 โฒ ๐ 4 โฒ ๐ 5 โฒ ๐ 6 โฒ ๐ 7 โฒ ๐ 8
Lifting Reduction to Asynchronous Programs Let ๐ be a procedure in program ๐ โข Reduction atomic action Q โ [๐ ] โ ๐ต โข Synchronization ๐ โ [๐ก๐ง๐๐(๐ )] โ ๐ต contains asynchronous replaces asynchronous invocations invocations with synchronous ones
Synchronization Example global var x proc Main(n): Traces of x: 0 1 2 1 0 -1 -2 -1 0 ... 0 var i := 0 0 1 2 3 4 3 2 3 2 โฆ 0 while i < n: async [x := x + 1] โฆ async [x := x โ 1] i := i + 1 global var x proc Main(n): var i := 0 Trace of x: 0 1 0 1 0 1 0 โฆ 0 while i < n: [x := x + 1] [x := x โ 1] i := i + 1 โผ atomic Main(n): skip
Termination? global var x proc Main: proc Main(n): async Foo Failure reachable var i := 0 assert false while i < n: async [x := x + 1] proc Foo: async [x := x โ 1] while (true): skip i := i + 1 global var x proc Main: proc Main(n): call Foo var i := 0 assert false Failure unreachable while i < n: [x := x + 1] proc Foo: [x := x โ 1] while (true): skip i := i + 1 โผ โผ atomic Main(n): atomic Main: skip assume false
Termination? Cooperation! global var x global var x proc Main: proc Main(n): async Foo proc Main(n): var i := 0 assert false var i := 0 while i < n: while i < n: async [x := x + 1] proc Foo: async [x := x + 1] async [x := x โ 1] while (true): skip async [x := x โ 1] i := i + 1 if *: i := i + 1 global var x global var x proc Main: proc Main(n): proc Main(n): call Foo var i := 0 var i := 0 assert false while i < n: while i < n: [x := x + 1] [x := x + 1] proc Foo: [x := x โ 1] [x := x โ 1] while (true): skip if *: i := i + 1 i := i + 1 โผ โผ โผ atomic Main(n): atomic Main: atomic Main(n): skip assume false skip
Pending Asynchronous Calls ๐ โ [๐ก๐ง๐๐(๐ )] โ ๐ต contains asynchronous replaces asynchronous invocations invocations with synchronous ones Example: Lock Service global var lock : nat? proc Acquire(tid : nat) s := false while (!s) call s := CAS(lock,NIL,tid) async Callback(tid)
Pending Asynchronous Calls ๐ โ [๐ก๐ง๐๐(๐ )] โ ๐ต contains asynchronous replaces SOME asynchronous contains invocations invocations with synchronous ones pending asyncs Example: Lock Service global var lock : nat? global var lock : nat? proc Acquire(tid : nat) atomic ACQUIRE(tid : nat) s := false assume lock == NIL while (!s) lock := tid call s := CAS(lock,NIL,tid) async Callback(tid) async Callback(tid)
Example: Lock Service Server Client proc Acquire(tid: nat) proc Callback(tid: nat) s := false t := x while (!s) x := t + 1 call s := CAS(lock,NIL,tid) async Release(tid) async Callback(tid) By synchronization proc Release(tid: nat) lock := nil left CALLBACK(tid: nat) assert lock == tid x := x + 1 lock := nil By synchronization By async elimination atomic ACQUIRE(tid: nat) atomic ACQUIRE โ( tid: nat) assume lock == NIL assume lock == NIL lock := tid lock := tid async Callback(tid) x := x + 1 lock := nil left RELEASE(tid: nat) assert lock == tid By abstraction lock := nil atomic ACQUIRE โโ( tid: nat) x := x + 1
Synchronizing Asynchrony II Synchronization transforms procedure Q into atomic action A ๐ฉ๐๐๐๐๐ ๐๐๐ : (1) execution steps of ๐ match (right)*(none)?(left)* (2) execution steps in asynchronous threads of ๐ match (left)*
Synchronizing Asynchrony II Synchronization transforms procedure Q into atomic action A ๐ซ๐๐๐๐๐๐๐๐๐๐ : partial sequential executions of ๐ must have some terminating extension โฎ
Multi-layered Refinement Proofs ๐ 1 โผ ๐ 2 โผ โฏ โผ ๐ ๐โ1 โผ ๐ ๐ ๐ is safe ๐ ๐ 1 is safe Advantages of structured proofs: Better for humans: easier to construct and maintain Better for computers: localized/small checks ๏ easier to automate Layered Programs [Hawblitzl, Petrank, Qadeer, Tasiran 2015] [K, Qadeer 2018] Express ๐ 1 , โฏ , ๐ ๐ (and their connection) as single entity
Lock Service (Layered Program) // Global variables // Server // Primitive atomic actions var lock@[1,3] : nat? atomic ACQUIRE@[2,3](tid: nat) atomic CAS@[1,1](old, new: nat?) var x @[1,4] : int assume lock == NIL returns (s: bool) lock := tid if (lock == old) async Callback(tid) lock := new s := true else left RELEASE@[2,2](tid: nat) s := false assert lock == tid // Client lock := NIL atomic RESET@[1,1]() lock := NIL left CALLBACK@[3,3](tid: nat) proc Acquire@1(tid: nat) assert lock == tid refines ACQUIRE both READ@[1,2](tid: nat) x := x + 1 var s: bool returns (v: int) lock := NIL s := false assert lock == tid while (!s) call s := CAS(NIL, tid) v := x proc Callback@2(tid: nat) async Callback(tid) refines CALLBACK both WRITE@[1,2](tid: nat, v: int) var t: int proc Release@1(tid: nat) assert lock == tid call t := READ(tid) refines RELEASE x := v call WRITE(tid, t+1) call RESET() async Release(tid)
Lock Service (Layer 1) // Global variables // Server // Primitive atomic actions var lock@[1,3] : nat? atomic ACQUIRE@[2,3](tid: nat) atomic CAS@[1,1](old, new: nat?) var x @[1,4] : int assume lock == NIL returns (s: bool) lock := tid if (lock == old) async Callback(tid) lock := new s := true else left RELEASE@[2,2](tid: nat) s := false assert lock == tid // Client lock := NIL atomic RESET@[1,1]() lock := NIL left CALLBACK@[3,3](tid: nat) proc Acquire@1(tid: nat) assert lock == tid refines ACQUIRE both READ@[1,2](tid: nat) x := x + 1 var s: bool returns (v: int) lock := NIL s := false assert lock == tid while (!s) call s := CAS(NIL, tid) v := x proc Callback@2(tid: nat) async Callback(tid) refines CALLBACK both WRITE@[1,2](tid: nat, v: int) var t: int proc Release@1(tid: nat) assert lock == tid call t := READ(tid) refines RELEASE x := v call WRITE(tid, t+1) call RESET() async Release(tid)
Recommend
More recommend