1 / 71

A Statically Verifiable Programming Model for Concurrent Object-Oriented Programs

A Statically Verifiable Programming Model for Concurrent Object-Oriented Programs. Bart Jacobs 1 (≠ Prof. B. P. F. Jacobs, R.U.Nijmegen, The Netherlands) Joint work with Jan Smans 1 , Frank Piessens 1 , Wolfram Schulte 2 , Rustan Leino 2 1 K.U.Leuven, Belgium

carla-wynn
Download Presentation

A Statically Verifiable Programming Model for Concurrent Object-Oriented Programs

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 Statically Verifiable Programming Model for Concurrent Object-Oriented Programs Bart Jacobs1 (≠ Prof. B. P. F. Jacobs, R.U.Nijmegen, The Netherlands) Joint work with Jan Smans1, Frank Piessens1, Wolfram Schulte2 , Rustan Leino2 1 K.U.Leuven, Belgium 2 Microsoft Research, Redmond, WA, USA

  2. Outline of the Talk • Progressive refinement of the approach: • Absence of data races • Absence of deadlocks • Ownership • Object invariants • Immutable objects • Related work, future work, conclusion

  3. The Problem • Goal: Delivering correct concurrent Java programs • Problem: It’s hard to reason about such programs • due to the non-local nature of data races, deadlocks, and object aliasing • Proposed solution: • A programming regime (or programming model) • that prevents data races • and deadlocks, • and enables local reasoning in the presence of object aliasing. • An annotation syntax and verification approach • that enables modular static verification of compliance with the programming model.

  4. Outline of the Talk • Progressive refinement of the approach: • Absence of data races • Absence of deadlocks • Ownership • Object invariants • Immutable objects • Related work, future work, conclusion

  5. Data Races class Account { int balance; } Account act = …; int b0 = act.balance; act.balance += 50; int b1 = act.balance; b1 == b0 + 50? Not necessarily!

  6. Data Races class Account { int balance; } Account act = …; int b0, b1; synchronized (act) { b0 = act.balance; act.balance += 50; b1 = act.balance; } b1 == b0 + 50? Not necessarily!

  7. class Account { int balance; } Account act = …; int b0, b1; synchronized (act) { b0 = act.balance; act.balance += 50; b1 = act.balance; } b1 == b0 + 50? Not necessarily! In Java, it’s not sound to reason sequentially about sequential code due to the possibility of data races. Data Races

  8. Preventing data races: Access sets • Per-thread access set t.A • x = o.f; or o.f = x; requires o in t.A • Always: t1 != t2 ==> t1.A and t2.A disjoint • Object can move between access sets only via proper synchronization

  9. Preventing data races: Access sets • x := new C; implies x in t.A’ • entering synchronized (o) implies o in t.A’ • exiting synchronized (o) implies o not in t.A’

  10. Preventing data races: Access sets • x := new C; implies x in t.A’ • entering synchronized (o) implies o in t.A’ • exiting synchronized (o) implies o not in t.A’ • But: races between creating thread and locking thread?

  11. Preventing data races:Shared and unshared objects • x := new C; implies x is unshared’ • attempting synchronized (o) requires x is shared • programmer indicates share o; • requires o in t.A and o is unshared • implies o is not in t.A’ and o is shared’ • shared objects never become unshared again • Property: If a shared object o is not locked by any thread, then it is not in any thread’s access set.

  12. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A

  13. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A x

  14. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A x

  15. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A x

  16. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A x t2.A

  17. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A x t2.A

  18. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A x t2.A

  19. Preventing data races // thread 1: x := new C; x.f := 5; share x; // start thread 2 // thread 2: synchronized (x) { int n := x.f; } shared t1.A x t2.A

  20. Thread creation • new Thread(r).start() • requires r in t.A • implies r not in t.A’ • in new thread: r in t.A

  21. Preventing Data Races: Lock Re-entry • Causes difficulties for method effect framing • We disallow it

  22. Preventing data races:Modular Static Verification • Annotations required: • Share commands • Method contracts • requires/ensures o is in tid.A (tid = current thread) • requires/ensures o is unshared/shared • requires/ensures o is not in tid.lockset/tid.lockset is empty • Field modifier: shared • Verification approach: • Verification condition generation • On entry to synchronized block: • foreach (o.f where o not in t.A) { o.f := random; } • Method effect framing: required access sets

  23. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example

  24. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main main.A shared

  25. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main main.A shared c

  26. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main main.A shared c

  27. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main main.A shared s1 c

  28. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main main.A shared s1 c

  29. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main s1 main.A shared c s1 s1.A

  30. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main s1 main.A shared s2 c s1 s1.A

  31. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main s1 main.A shared s2 c s1 s1.A

  32. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example s2 s1 main.A shared c s1 s2 s2.A s1.A

  33. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example s2 s1 main.A shared c s1 s2 s2.A s1.A

  34. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example s2 main.A s1 shared c s1 s2 s2.A s1.A

  35. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example s2 main.A shared c s1 s2 s2.A s1.A

  36. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example s2 main.A shared c s1 s2 s2.A s1.A

  37. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main.A s2 shared c s1 s2 s2.A s1.A

  38. class Counter { int count; } class Session implements Runnable { shared Counter counter; public void run() requires this in tid.A; requirestid.lockset is empty; { synchronized (counter) { counter.count++; } } } Counter counter = new Counter(); share counter; Session session1 = new Session(); session1.counter = counter; new Thread(session1).start(); Session session2 = new Session(); session2.counter = counter; new Thread(session2).start(); Preventing data races: Example main.A shared c s1 s2 s2.A s1.A

  39. Outline of the Talk • Progressive refinement of the approach: • Absence of data races • Absence of deadlocks • Ownership • Object invariants • Immutable objects • Related work, future work, conclusion

  40. Preventing deadlocks • Deadlock = cycle of threads each waiting for the next to release a lock • Proposed solution: • Programmer constructs partially ordered set of lock levels • using l := between({ll1,...,lln},{lu1,...,lum}); annotation • Assigns a lock level to each shared object • A thread may attempt to acquire an object’s lock only if the object is below the objects whose lock it already holds

  41. Preventing deadlocks: example class Fork {} class Philosopher implements Runnable { shared Fork fa, fb; Philosopher(Fork fa, Fork fb) requires fa.locklevel < fb.locklevel; { this.fa = fa; this.fb = fb; } public void run() requiresthis in tid.A and lockset is empty; { synchronized (fb) { synchronized (fa) { /* eat */ } } } } Fork f1 = new Fork(); locklevel l1 = between({},{}); share f1 at l1; Fork f2 = new Fork(); locklevel l2 = between({l1},{}); share f2 at l2; Fork f3 = new Fork(); locklevel l3 = between({l2},{}); share f3 at l3; new Thread(new Philosopher(f1, f2)).start(); new Thread(new Philosopher(f2, f3)).start(); new Thread(new Philosopher(f1, f3)).start();

  42. Outline of the Talk • Progressive refinement of the approach: • Absence of data races • Absence of deadlocks • Ownership • Object invariants • Immutable objects • Related work, future work, conclusion

  43. Rep Objects • Objects often use auxiliary objects to help represent their state • e.g. an ArrayList uses an array object • We wish to protect the array object against data races using the lock of the ArrayList object • Proposed solution: use Spec#’s ownership system • Fields may be marked with rep modifier • The objects pointed to by o’s rep fields are its rep objects • An object may be packed or unpacked • When an object is in the packed state, it owns its rep objects • A thread can gain access to an owned object by locking the owner and then unpacking the owner

  44. Ownership system • When packing an object o, using a pack o; annotation, o’s rep objects are removed from tid.A • When unpacking o, using an unpack o; annotation, o’s rep objects are added to tid.A

  45. Ownership system: Example class BoundedList { rep Object[] elements; int count; BoundedList(int capacity) ensuresthis is in tid.A and this is packed; { elements = new Object[capacity]; pack this; } void add(Object value) requiresthis is in tid.A and this is packed; ensuresthis is in tid.A and this is packed; { unpack this; elements[count++] = value; pack this; } } BoundedList bl = new BoundedList(); share bl; synchronized (bl) { bl.add(null); }

  46. Ownership system: Example class BoundedList { rep Object[] elements; int count; BoundedList(int capacity) ensuresthis is in tid.A and this is packed; { elements = new Object[capacity]; pack this; } void add(Object value) requiresthis is in tid.A and this is packed; ensuresthis is in tid.A and this is packed; { unpack this; elements[count++] = value; pack this; } } BoundedList bl = new BoundedList(); share bl; synchronized (bl) { bl.add(null); } shared t.A

  47. Ownership system: Example class BoundedList { rep Object[] elements; int count; BoundedList(int capacity) ensuresthis is in tid.A and this is packed; { elements = new Object[capacity]; pack this; } void add(Object value) requiresthis is in tid.A and this is packed; ensuresthis is in tid.A and this is packed; { unpack this; elements[count++] = value; pack this; } } BoundedList bl = new BoundedList(); share bl; synchronized (bl) { bl.add(null); } shared t.A bl

  48. Ownership system: Example class BoundedList { rep Object[] elements; int count; BoundedList(int capacity) ensuresthis is in tid.A and this is packed; { elements = new Object[capacity]; pack this; } void add(Object value) requiresthis is in tid.A and this is packed; ensuresthis is in tid.A and this is packed; { unpack this; elements[count++] = value; pack this; } } BoundedList bl = new BoundedList(); share bl; synchronized (bl) { bl.add(null); } shared t.A bl es

  49. Ownership system: Example class BoundedList { rep Object[] elements; int count; BoundedList(int capacity) ensuresthis is in tid.A and this is packed; { elements = new Object[capacity]; pack this; } void add(Object value) requiresthis is in tid.A and this is packed; ensuresthis is in tid.A and this is packed; { unpack this; elements[count++] = value; pack this; } } BoundedList bl = new BoundedList(); share bl; synchronized (bl) { bl.add(null); } shared t.A bl es

  50. Ownership system: Example class BoundedList { rep Object[] elements; int count; BoundedList(int capacity) ensuresthis is in tid.A and this is packed; { elements = new Object[capacity]; pack this; } void add(Object value) requiresthis is in tid.A and this is packed; ensuresthis is in tid.A and this is packed; { unpack this; elements[count++] = value; pack this; } } BoundedList bl = new BoundedList(); share bl; synchronized (bl) { bl.add(null); } shared t.A bl es

More Related