1 / 67

A Parameterized Type System for Race-Free Java Programs

A Parameterized Type System for Race-Free Java Programs. Chandrasekhar Boyapati Martin Rinard Laboratory for Computer Science Massachusetts Institute of Technology {chandra, rinard}@lcs.mit.edu. Data races in multithreaded programs. Two threads concurrently access same data

bforeman
Download Presentation

A Parameterized Type System for Race-Free Java 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 Parameterized Type System for Race-Free Java Programs Chandrasekhar Boyapati Martin Rinard Laboratory for Computer Science Massachusetts Institute of Technology {chandra, rinard}@lcs.mit.edu

  2. Data races in multithreaded programs • Two threads concurrently access same data • At least one access is a write • No synchronization to separate accesses Thread 1: x = x + 1; Thread 2: x = x + 2;

  3. Why data races are a problem • Some correct programs contain data races • But most races are programming errors • Code intended to execute atomically • Synchronization omitted by mistake • Consequences can be severe • Non-deterministic, timing-dependent bugs • Difficult to detect, reproduce, eliminate

  4. Avoiding data races Thread 1: x = x + 1; Thread 2: x = x + 2;

  5. Avoiding data races Thread 1: lock(l); x = x + 1; unlock(l); Thread 2: lock(l); x = x + 2; unlock(l); • Associate a lock with every shared mutable data • Acquire lock before data access • Release lock after data access

  6. Avoiding data races Thread 1: lock(l); x = x + 1; unlock(l); Thread 2: lock(l); x = x + 2; unlock(l); Problem: Locking is not enforced! Inadvertent programming errors…

  7. Our solution • A static type system for OO programs • Well-typed programs are free of races

  8. Our solution • A static type system for OO programs • Well-typed programs are free of races • Programmers specify • How each object is protected from races • In types of variables pointing to objects • Type checkers statically verify • That objects are used only as specified

  9. Protection mechanism of an object • Specifies the lock protecting the object, or • Specifies that object needs no locks b’cos • The object is immutable, or • The object is not shared, or • There is a unique pointer to the object

  10. Types are proofs Java Java Type checker Translator (Removes extra types) + Extra types Compiler bytecodes JVM

  11. Outline • Motivation • Type system • Experience • Related work • Conclusions

  12. Race-free Account program class Account { int balance = 0; int deposit(int x) { this.balance += x; } } Account a1 = new Account; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; Account a2 = new Account; a2.deposit(10);

  13. Race-free Account program Concurrent Java: Java: class Account { int balance = 0; int deposit(int x) { this.balance += x; } } Account a1 = new Account; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; Account a2 = new Account; a2.deposit(10);  Thread t; t.start(); fork (t) { t.start(); }

  14. Race-free Account program class Account { int balance = 0; int deposit(int x) { this.balance += x; } } Account a1 = new Account; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; Account a2 = new Account; a2.deposit(10);

  15. Statically verifiable race-free program class AccountthisOwner { int balance = 0; int deposit(int x) requires (this) { this.balance += x; } } final Accountself a1 = new Accountself; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; AccountthisThread a2 = new AccountthisThread; a2.deposit(10);

  16. Statically verifiable race-free program class AccountthisOwner { int balance = 0; int deposit(int x) requires (this) { this.balance += x; } } final Accountself a1 = new Accountself; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; AccountthisThread a2 = new AccountthisThread; a2.deposit(10); thisOwner protects the Account

  17. Statically verifiable race-free program class AccountthisOwner { int balance = 0; int deposit(int x) requires (this) { this.balance += x; } } final Accountself a1 = new Accountself; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; AccountthisThread a2 = new AccountthisThread; a2.deposit(10); a1 is protected by its lock a2 is thread-local

  18. Statically verifiable race-free program class AccountthisOwner { int balance = 0; int deposit(int x) requires (this) { this.balance += x; } } final Accountself a1 = new Accountself; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; AccountthisThread a2 = new AccountthisThread; a2.deposit(10); deposit requires lock on “this”

  19. Statically verifiable race-free program class AccountthisOwner { int balance = 0; int deposit(int x) requires (this) { this.balance += x; } } final Accountself a1 = new Accountself; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; fork (a1) { synchronized (a1) in { a1.deposit(10); } }; AccountthisThread a2 = new AccountthisThread; a2.deposit(10); a1 is locked before calling deposit a2 need not be locked

  20. Type system • Basic type system: Locks, thread-local objects • Object ownership • Type system • Type inference • Extensions: Unique pointers, read-only objects

  21. thisThread thisThread Thread2 objects Potentially shared objects Thread1 objects Object ownership • Every object has an owner • An object can be owned by • Itself • Another object • Special per-thread owner called thisThread

  22. thisThread thisThread Thread2 objects Potentially shared objects Thread1 objects Ownership properties • Owner of an object does not change over time • Ownership relation forms a forest of rooted trees • Roots can have self loops

  23. thisThread thisThread Thread2 objects Potentially shared objects Thread1 objects Ownership properties • Every object is protected by its root owner • To gain exclusive access to an object, it is • Necessary and sufficient to lock its root owner • A thread implicitly holds the lock on its thisThread

  24. Basic type system • Object ownership • Type system • Type inference

  25. head TStack next next next value value value TNode’s … … … T’s TStack program class TStack { TNode head; void push(T value) {…} T pop() {…} } class TNode { TNode next; T value; … } class T {…}

  26. TStack TNode’s T’s TStack program class TStackthisOwner, TOwner { TNodethis, TOwner head; … } class TNodethisOwner, TOwner { TNodethisOwner, TOwner next; TTOwner value; … } TStackthisThread, thisThread s1; TStackthisThread, self s2;

  27. TStack TNode’s T’s Parameterizing classes class TStackthisOwner, TOwner { TNodethis, TOwner head; … } class TNodethisOwner, TOwner { TNodethisOwner, TOwner next; TTOwner value; … } TStackthisThread, thisThread s1; TStackthisThread, self s2; Classes are parameterized with one or more owners First owner owns the “this” object

  28. TStack TNode’s T’s Instantiating classes class TStackthisOwner, TOwner { TNodethis, TOwner head; … } class TNodethisOwner, TOwner { TNodethisOwner, TOwner next; TTOwner value; … } TStackthisThread, thisThread s1; TStackthisThread, self s2; Classes can be instantiated with final expressions E.g., with “this”

  29. TStack TNode’s T’s Instantiating classes class TStackthisOwner, TOwner { TNodethis, TOwner head; … } class TNodethisOwner, TOwner { TNodethisOwner, TOwner next; TTOwner value; … } TStackthisThread, thisThread s1; TStackthisThread, self s2; Classes can be instantiated with formal parameters E.g., with “thisOwner” or “TOwner”

  30. TStack TNode’s T’s thisThread Instantiating classes class TStackthisOwner, TOwner { TNodethis, TOwner head; … } class TNodethisOwner, TOwner { TNodethisOwner, TOwner next; TTOwner value; … } TStackthisThread, thisThread s1; TStackthisThread, self s2; Classes can be instantiated with “thisThread”

  31. thisThread TStack TNode’s T’s Instantiating classes class TStackthisOwner, TOwner { TNodethis, TOwner head; … } class TNodethisOwner, TOwner { TNodethisOwner, TOwner next; TTOwner value; … } TStackthisThread, thisThread s1; TStackthisThread, self s2; Classes can be instantiated with “self”

  32. Requires clauses class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … } Methods can require threads to have locks on root owners of objects

  33. head TStack next next next value value value TNode’s … … … T’s Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … }

  34. Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … } Locks held thisThread, RootOwner(this)

  35. Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … } Locks held thisThread, RootOwner(this) Locks required RootOwner(this)

  36. Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … } Locks held thisThread, RootOwner(this) Locks required ?

  37. Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … } Locks held thisThread, RootOwner(this) Locks required RootOwner(head)

  38. Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … } Locks held thisThread, RootOwner(this) Locks required RootOwner(head) = RootOwner(this)

  39. Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … } Locks held thisThread, RootOwner(this) Locks required RootOwner(this), RootOwner(head) = RootOwner(this)

  40. Type checking pop method class TStackthisOwner, TOwner { TNodethis, TOwner head; … TTOwner pop() requires (this) { if (head == null) return null; TTOwner value = head.value(); head = head.next(); return value; } } class TNodethisOwner, TOwner { TTOwner value() requires (this) {…} TNodethisOwner, TOwner next() requires (this) {…} … }

  41. Type checking client code class TStackthisOwner, TOwner { TTOwner pop() requires (this) {…} … } final TStackself, self s = …; fork (s) { synchronized (s) in { s.pop(); } };

  42. Type checking client code class TStackthisOwner, TOwner { TTOwner pop() requires (this) {…} … } final TStackself, self s = …; fork (s) { synchronized (s) in { s.pop(); } }; Locks held thisThread, s

  43. Type checking client code class TStackthisOwner, TOwner { TTOwner pop() requires (this) {…} … } final TStackself, self s = …; fork (s) { synchronized (s) in { s.pop(); } }; Locks held thisThread, s Locks required RootOwner(s) = s

  44. Basic type system • Object ownership • Type system • Type inference

  45. Inferring owners of local variables class Aoa1, oa2 {…} class Bob1, ob2, ob3 extends Aob2, ob3 {…} class C { void m(BthisThread, this, oc1 b) { A a1; B b1; b1 = b; a1 = b1; } }

  46. Inferring owners of local variables class Aoa1, oa2 {…} class Bob1, ob2, ob3 extends Aob2, ob3 {…} class C { void m(BthisThread, this, oc1 b) { Ax1, x2 a1; Bx3, x4, x5 b1; b1 = b; a1 = b1; } } Augment unknown types with owners

  47. Inferring owners of local variables class Aoa1, oa2 {…} class Bob1, ob2, ob3 extends Aob2, ob3 {…} class C { void m(BthisThread, this, oc1 b) { Ax1, x2 a1; Bx3, x4, x5 b1; b1 = b; a1 = b1; } } Gather constraints x3 = thisThread x4 = this x5 = oc1

  48. Inferring owners of local variables class Aoa1, oa2 {…} class Bob1, ob2, ob3 extends Aob2, ob3 {…} class C { void m(BthisThread, this, oc1 b) { Ax1, x2 a1; Bx3, x4, x5 b1; b1 = b; a1 = b1; } } Gather constraints x3 = thisThread x4 = this x5 = oc1 x1 = x4 x2 = x5

  49. Inferring owners of local variables class Aoa1, oa2 {…} class Bob1, ob2, ob3 extends Aob2, ob3 {…} class C { void m(BthisThread, this, oc1 b) { Athis, oc1 a1; BthisThread, this, oc1 b1; b1 = b; a1 = b1; } } Solve constraints x3 = thisThread x4 = this x5 = oc1 x1 = x4 x2 = x5

  50. Inferring owners of local variables class Aoa1, oa2 {…} class Bob1, ob2, ob3 extends Aob2, ob3 {…} class C { void m(BthisThread, this, oc1 b) { Athis, oc1 a1; BthisThread, this, oc1 b1; b1 = b; a1 = b1; } } Solve constraints x3 = thisThread x4 = this x5 = oc1 x1 = x4 x2 = x5 • Only equality constraints between owners • Takes almost linear time to solve

More Related