1 / 36

Wolfram Schulte & Bart Jacobs Microsoft Research & K.U.Leuven TV 2006

A simple sequential reasoning approach for sound modular verification of mainstream multithreaded programs. Wolfram Schulte & Bart Jacobs Microsoft Research & K.U.Leuven TV 2006. Agenda. Background - Wolfram Spec#: A verifying compiler(*) Foreground - Bart

matsu
Download Presentation

Wolfram Schulte & Bart Jacobs Microsoft Research & K.U.Leuven TV 2006

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. A simple sequential reasoning approach for sound modular verification of mainstream multithreaded programs Wolfram Schulte & Bart Jacobs Microsoft Research & K.U.Leuven TV 2006

  2. Agenda Background - Wolfram • Spec#: A verifying compiler(*) Foreground - Bart • Verification of Data Race Freedom • Verification of Invariants • Verification of Deadlock Freedom __________ (*) Joint work with Rustan Leino, Mike Barnett, Manuel Fähndrich, Herman Venter, Rob DeLine, and Wolfram Schulte (all MSR), Peter Müller and Adam Darvas (ETH), Bart Jacobs and Jan Smans (KU Leuven), Bor-Yuh Evan Chang (Berkley), and Angelika Wallenburg (Chalmers)

  3. A Verifying Compiler “A verifying compiler uses automated .. reasoning to check the correctnessof the program that it compiles. Correctness is specified by types, assertions, .. and other redundant annotationsthat accompany the program.” [Hoare, 2004]

  4. The Spec# Verifying Compiler • As source language we use C# • As specifications we use method contracts, invariants, and also class, field and type annotations • As program logic we use Dijkstra’s weakest preconditions • For automatic verification we use type checking, verification condition generation and automatic theorem proving

  5. Spec#: Research Challenge How to verify object invariantsin the presence of • Callbacks • Aliasing • Multi-threading

  6. classSubject{ Observer obs; int state=1; invariant state !=0; Subject(Observer o){ this.obs = o; o.sub = this; } void Update(int y) { state = 0; obs.Notify(); state = y; } int Get() { return Program.N/state; } classObserver{ Subject sub; int cache; void Notify() { cache = sub.Get(); } } Example: Callbacks and Invariants classProgram{ static int N = 100; static void Main() { Observer o = new Observer(); Subject s = new Subject(o); s.Update(5); }

  7. Example: Callbacks and Invariants Observer o Subject s Invariant holds Update(x) Invariant might be broken Notify() Execution within Update Get() callback Invariant holds Invariant might be broken Time Invariant holds

  8. Callbacks and Invariants:Allow invariants temporarily to be broken Design • Objects can be valid or mutable • Mutable objects need not satisfy their invariants • Mutability is changed by special source commands • unpack(o)to make o mutable • pack(o) to re-establish invariant and make o valid • Only fields of mutable objects can be updated

  9. classSubject{ Observer obs; int state=1; invariantstate!=0; Subject(Observer o){ this.obs = o; o.sub = this; } void Update(int y) { unpack(o); state = y; pack(o); obs.Notify(); } int Get() { return Program.N/state; } classObserver{ Subject sub; int cache; invariant sub!=null cache== Program.N/sub.state; void Notify() { cache = sub.Get(); } } Example: Invariants and Aliasing classProgram{ static int N = 100; static void Main() { Observer o = new Observer(); Subject s = new Subject(o); s.Update(5); }

  10. Example: Invariants and Aliasing Notify() Update(5) o: Observer cache==100/sub.statecache == 10 : Subject invariant state!=0 state ==10 Get() obs 5 20 sub Valid inv = Mutable

  11. Invariants and Aliasing:Use corresponding proof / design patterns Design When an object is mutated, then all of its dependents must be mutable Two kinds of dependencies: • Visibility for peer structures • An object whose field is mentioned in an invariant needs to know its dependents • Ownership for hierarchical abstraction • An object does not need to know its owner • An object o’s invariant may only depend on the fields of o and the fields of objects (transitively) owned by o

  12. A Simple Sequential Reasoning Approach for Sound Modular Verification of Mainstream Multithreaded Programs

  13. Demo: Verifying a Chat Server

  14. Outline of the talk • Data race prevention • Invariants and ownership trees • Deadlock prevention

  15. Multiple threads running in parallel, reading and writing shared data A data race occurs when a shared variable is written by one thread and concurrently read or written by another thread How to guarantee that there are no data races? class Counter { int dangerous; void Inc() {int tmp = dangerous; dangerous = tmp + 1; } } Counter ct = new Counter(); new Thread(ct.Inc).Start(); new Thread(ct.Inc).Start(); // What is the value of // ct.dangerous after both // threads have terminated? Multithreading

  16. Mutexes: Avoiding Races • Mutual exclusion for shared objects is provided via locks • Locks can be obtained using a lock block. A thread may enter a lock (o) block only if no other thread is executing inside a lock (o) block; else, the thread waits • When a thread holds a lock on object o, C#/Java • do prevent other threads from locking o but • do not prevent other threads from accessing o’s fields

  17. Program Method for Avoiding Races Our program rules enforce that a thread t can only access a field of object o if o is either thread local or t has locked o We model accessibility using access sets: • A thread’s access set consists of all objects it has created but not shared yet or whose lock it holds. • Threads are only allowed to access fields of objects in their corresponding access set Our program rules prevent data races by ensuring that access sets of different threads never intersect.

  18. Annotations Needed to Avoid Races • Threads have access sets • t.Ais a new ghost field in each thread t describing the set of accessible objects • Objects can be shared • o.sharedis a new boolean ghost field in each object o • share(o) is a new operation that shares an unshared o • Fields can be declared to be shared • Shared fields can only be assigned shared objects.

  19. Object Life Cycle and Access Sets • A new o is unshared, and added to tid.A. • An unshared o can be made accessible for other threads by sharing it; o is taken out of tid.A • A shared o can be exclusively acquired by locking it; when locking succeeds o is added to tid.A • A locked o can be released for others by unlocking it; o is taken out of tid.A. acquire free locked new share release unshared shared

  20. Tr[[o = new C();]] = … o.shared:= false; tid.A[o]:= true Tr[[x = o.f;]] = … assert tid.A[o]; x :=o.f; Tr[[o.f = x;]] = … assert tid.A[o]; if (f is declared shared) assert x.shared; o.f :=x; Tr[[share(o)]] = … assert tid.A[o]; assert ! o.shared; o.shared :=true; tid.A[o] :=false; Tr[[lock (o) S ]] = … assert ! tid.A[o]; assert o.shared; havoc o.*; tid.A[o]:=true; Tr[[S]]; tid.A[o]:= false Verification via Access Sets

  21. Verification via Access Sets The need for havoc on the lock rule. When a thread acquires o, this is modeled by assigning an arbitrary new value to o’s fields, since o might have been changed by another thread int x; lock (o) { x = o.f; } lock (o) { assert x == o.f; // fails }

  22. Example for Data Race Freedom Counter ct = new Counter(); share(ct); new Thread(delegate () { lock (ct) ct.Inc(); }).Start(); new Thread(delegate () { lock (ct) ct.Inc(); }).Start();

  23. class Session { shared Counter ct ; int id; Session(Counter ct , int id) requires ct.shared; ensures tid.A[this] ∧ ! this.shared; { this.ct=ct; this.id=id; } void Run() requirestid.A[this]; { for (; ; ) lock (this.ct) this.ct.Inc(); }}} // thread t0 Counter ct = new Counter(); share(ct); Session s1 =new Session(ct,1); Session s2 =new Session(ct,2); // transfers s1 to t1t1 = newThread(s1.Run); // transfers s2 to t2t2 = new Thread(s2.Run); t1.Start(); t2.Start(); Example for Data Race Freedom

  24. Soundness Theorem •  threads t1,t2 :: t1t2  t1.A  t2.A =  •  object o, thread t :: o.shared && o ∈ t.A  t holds o’s lock • Proof sketch for Theorem • new • share (o) • Entry into lock (o) • Exit from lock (o) Corollary • Valid programs don’t have data races

  25. Outline of the talk • Data race prevention • Invariants and ownership trees • Deadlock prevention

  26. Invariants and Concurrency Invariants, whether over a single object or over an ownership tree, can be protected via a single lock (coarse grained locking) For sharing and locking we • require an object o to be valid when o becomes free. This ensures that when a thread locks o, it can assume o’s invariant For invariants involving owned objects • pack(o) deletes o’s ownedobjects from the thread’s access set, • unpack(o) adds o’s ownedobjects to the thread’s access set,

  27. Tr[[pack o;]] =assert tid.A[o];assert ! o.inv; assert c: c.owner = o  tid.A[c] ∧ c.inv; foreach (c | c.owner = o) { tid.A[c] := false; } assert Inv( o ); o.inv := true; Tr[[unpack o;]] = assert tid.A[o];assert o.inv;foreach (c | c.owner = o) { tid.A[c] := true; } o.inv := false; Verifying Multi-threaded Pack/Unpack

  28. Ownership: Verifying Lock Blocks Finally, when locking we also have to “forget the knowledge about” (= assign an arbitrary value to) owned objects Tr[[lock (o) S; ]] = assert o.shared; assert ! tid.A[o]; foreach (p | !tid.A[p]) havoc p.*; tid.A[o]:=true; Tr[[S]] ; tid.A[o]:= false;

  29. Outline of the talk • Data race prevention • Invariants and ownership trees • Deadlock prevention

  30. A deadlock occurs when a set of threads each wait for a mutex (i.e shared object) that another thread holds Methodology: partial order over all shared objects in each thread, acquire shared objects in descending order Dining Philosophers 1 has F1, waits for F2 2 has F2, waits for F3 3 has F3, waits for F1 Concurrency: Deadlocks 3 Fork 1 Fork 3 1 2 Fork 2

  31. Annotations Needed to Avoid Deadlocks We construct a partial order on shared objects, denoted by ≺. • When o is shared, we add edges to the partial order as specified in the share command’s where clause. (Specified lower bounds have to be less than specified upper bounds) • Each thread has a new ghost field lockstack, holding the set of acquired locks

  32. Tr[[lock (o) S ]] = assert o.shared; assert tid.lockstack != empty  o ≺ tid.lockstack.top(); tid.lockStack.push(o); foreach (p | !tid.A[p]) havoc p.*; tid.A[o]:=true; Tr[[S]] ; tid.A[o]:= false; tid.lockstack.pop(o); Tr[[share o where p ≺ o && o ≺ q;]] = assert o  tid.A; assert ! o.shared; tid.A[o] := false; o.shared := true; assert p ≺ q; assume p ≺ o && o ≺ q; Verification via Lock Ordering and Lockstacks

  33. f1 = new Fork(); share f1; f2 = new Fork(); share f2 where f1 ≺ f2; f3 = new Fork(); share f3 where f2 ≺ f3 ; P1 = new Thread( delegate() {lock (f2) { lock (f1) { /*eat*/ }}}); P1.Start(); P2 = new Thread( delegate() { lock (f3) { lock (f2) {/*eat*/ }}}); P2.Start(); P3 = new Thread( delegate() { lock (f3) { lock (f1) {/*eat*/ }}}); P3.Start(); Example: Deadlock Avoidance (contd.) Dining Philosophers 3 left right Fork 1 Fork 3 right left 1 2 right left Fork 2

  34. Conclusion • Clients can reason entirely as if world was single-threaded for non-shared objects • Supports caller-side locking and callee-side locking • Deadlocks are prevented by partially ordering shared objects

  35. Related Work • Type systems for safe concurrency • Checking is faster • Support fewer programs • Do not verify object invariants or other assertions • Separation logic • Theorem proving technology less mature • Not yet applied to Java-style locking • Rely/guarantee reasoning • Too cumbersome for simple programs • Is complementary • Atomicity • Builds on top of data race prevention methodology

  36. The End (for now) http://research.micsoft.com/specsharp

More Related