Download presentation
Presentation is loading. Please wait.
1
Ownership Types for Safe Programming: Preventing Data Races and Deadlocks Chandrasekhar Boyapati Robert Lee Martin Rinard Laboratory for Computer Science Massachusetts Institute of Technology {chandra, rhlee, rinard}@lcs.mit.edu
2
Thread 1: x = x + 1; Thread 2: x = x + 2; Two threads access same data Two threads access same data At least one access is a write At least one access is a write No synchronization to separate accesses No synchronization to separate accesses Data Races in Multithreaded Programs
3
Avoiding Data Races Thread 1: x = x + 1; Thread 2: x = x + 2;
4
Avoiding Data Races Associate locks with shared mutable data Associate locks with shared mutable data Acquire lock before data access Acquire lock before data access Release lock after data access Release lock after data access Thread 1: lock(l); x = x + 1; unlock(l); Thread 2: lock(l); x = x + 2; unlock(l);
5
Thread 1 Thread 2 Thread n … Lock 1 Lock n Lock 2 Lock 3 Cycle of the form Cycle of the form Thread 1 holds Lock 1, waits for Lock 2 Thread 2 holds Lock 2, waits for Lock 3 … Thread n holds Lock n, waits for Lock 1 Deadlocks in Multithreaded Programs
6
Avoiding Deadlocks Thread 1 Thread 2 Thread n … Lock 1 Lock n Lock 2 Lock 3
7
Avoiding Deadlocks Thread 1 Thread 2 Thread n … Lock 1 Lock n Lock 2 Lock 3 Associate a partial order among locks Associate a partial order among locks Acquire locks in order Acquire locks in order ~ ~
8
Problem With Current Practice Locking discipline is not enforced Locking discipline is not enforced Inadvertent programming errors Inadvertent programming errors Can cause data races and deadlocks Consequences can be severe Consequences can be severe Non-deterministic, timing dependent bugs Difficult to detect, reproduce, eliminate
9
Our Solution Static type system Static type system Prevents both data races and deadlocks
10
Our Solution Static type system Static type system Prevents both data races and deadlocks Programmers specify Programmers specify How each object is protected from races Partial order among locks Type checker statically verifies Type checker statically verifies Objects are used only as specified Locks are acquired in order
11
Talk Outline Motivation Motivation Type system Type system Preventing data races Preventing deadlocks Experience Experience Related work Related work Conclusions Conclusions
12
Preventing Data Races Programmers specify for every object Programmers specify for every object Lock protecting the object, or That the object needs no locks because Object is immutable Object is thread-local Object has a unique pointer
13
Preventing Deadlocks Programmers specify lock ordering using Programmers specify lock ordering using Static lock levels Recursive data structures Mutable trees Monotonic DAGs Runtime ordering Type checker statically verifies Type checker statically verifies Locks are acquired in descending order Specified order is a partial order
14
Lock Level Based Partial Orders Lock levels are partially ordered Lock levels are partially ordered Locks belong to lock levels Locks belong to lock levels Threads must acquire locks in descending order of lock levels Threads must acquire locks in descending order of lock levels
15
Lock Level Based Partial Orders class CombinedAccount { final Account savingsAccount = new Account(); final Account savingsAccount = new Account(); final Account checkingAccount = new Account(); final Account checkingAccount = new Account(); int balance() { int balance() { synchronized (savingsAccount) { synchronized (savingsAccount) { synchronized (checkingAccount) { synchronized (checkingAccount) { return savingsAccount.balance + checkingAccount.balance; return savingsAccount.balance + checkingAccount.balance; }}} }}}}
16
class CombinedAccount { LockLevel savingsLevel; LockLevel savingsLevel; LockLevel checkingLevel < savingsLevel; LockLevel checkingLevel < savingsLevel; final Account self : savingsLevel savingsAccount = new Account(); final Account self : savingsLevel savingsAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); int balance() locks (savingsLevel) { int balance() locks (savingsLevel) { synchronized (savingsAccount) { synchronized (savingsAccount) { synchronized (checkingAccount) { synchronized (checkingAccount) { return savingsAccount.balance + checkingAccount.balance; return savingsAccount.balance + checkingAccount.balance; }}} }}}} Lock Level Based Partial Orders
17
class CombinedAccount { LockLevel savingsLevel; LockLevel savingsLevel; LockLevel checkingLevel < savingsLevel; LockLevel checkingLevel < savingsLevel; final Account self : savingsLevel savingsAccount = new Account(); final Account self : savingsLevel savingsAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); int balance() locks (savingsLevel) { int balance() locks (savingsLevel) { synchronized (savingsAccount) { synchronized (savingsAccount) { synchronized (checkingAccount) { synchronized (checkingAccount) { return savingsAccount.balance + checkingAccount.balance; return savingsAccount.balance + checkingAccount.balance; }}} }}}} checkingLevel < savingsLevel Lock Level Based Partial Orders
18
class CombinedAccount { LockLevel savingsLevel; LockLevel savingsLevel; LockLevel checkingLevel < savingsLevel; LockLevel checkingLevel < savingsLevel; final Account self : savingsLevel savingsAccount = new Account(); final Account self : savingsLevel savingsAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); int balance() locks (savingsLevel) { int balance() locks (savingsLevel) { synchronized (savingsAccount) { synchronized (savingsAccount) { synchronized (checkingAccount) { synchronized (checkingAccount) { return savingsAccount.balance + checkingAccount.balance; return savingsAccount.balance + checkingAccount.balance; }}} }}}} savingsAccount belongs to savingsLevel checkingAccount belongs to checkingLevel Lock Level Based Partial Orders
19
class CombinedAccount { LockLevel savingsLevel; LockLevel savingsLevel; LockLevel checkingLevel < savingsLevel; LockLevel checkingLevel < savingsLevel; final Account self : savingsLevel savingsAccount = new Account(); final Account self : savingsLevel savingsAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); int balance() locks (savingsLevel) { int balance() locks (savingsLevel) { synchronized (savingsAccount) { synchronized (savingsAccount) { synchronized (checkingAccount) { synchronized (checkingAccount) { return savingsAccount.balance + checkingAccount.balance; return savingsAccount.balance + checkingAccount.balance; }}} }}}} locks are acquired in descending order Lock Level Based Partial Orders
20
class CombinedAccount { LockLevel savingsLevel; LockLevel savingsLevel; LockLevel checkingLevel < savingsLevel; LockLevel checkingLevel < savingsLevel; final Account self : savingsLevel savingsAccount = new Account(); final Account self : savingsLevel savingsAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); int balance() locks (savingsLevel) { int balance() locks (savingsLevel) { synchronized (savingsAccount) { synchronized (savingsAccount) { synchronized (checkingAccount) { synchronized (checkingAccount) { return savingsAccount.balance + checkingAccount.balance; return savingsAccount.balance + checkingAccount.balance; }}} }}}} locks held by callers > savingsLevel Lock Level Based Partial Orders
21
class CombinedAccount { LockLevel savingsLevel; LockLevel savingsLevel; LockLevel checkingLevel < savingsLevel; LockLevel checkingLevel < savingsLevel; final Account self : savingsLevel savingsAccount = new Account(); final Account self : savingsLevel savingsAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); final Account self : checkingLevel checkingAccount = new Account(); int balance() locks (savingsLevel) { int balance() locks (savingsLevel) { synchronized (savingsAccount) { synchronized (savingsAccount) { synchronized (checkingAccount) { synchronized (checkingAccount) { return savingsAccount.balance + checkingAccount.balance; return savingsAccount.balance + checkingAccount.balance; }}} }}}} balance can acquire these locks Lock Level Based Partial Orders
22
Types Impose No Dynamic Overhead Typechecker Translator (Removes extra types) Compiler JVM Java bytecodes + Extra types types Java
23
Lock Level Based Partial Orders Bounded number of lock levels Bounded number of lock levels Unbounded number of locks Unbounded number of locks Lock levels support programs where the maximum number of locks simultaneously held by a thread is bounded Lock levels support programs where the maximum number of locks simultaneously held by a thread is bounded We use other mechanisms for other cases We use other mechanisms for other cases
24
Type System Preventing data races Preventing data races Preventing deadlocks using Preventing deadlocks using Static lock levels Recursive data structures Mutable trees Monotonic DAGs Runtime ordering
25
Tree Based Partial Orders Locks in a level can be tree-ordered Locks in a level can be tree-ordered Using data structures with tree backbones Using data structures with tree backbones Doubly linked lists Trees with parent/sibling pointers Threaded trees…
26
class Node { Node left; Node left; Node right; Node right; synchronized void rotateRight() { synchronized void rotateRight() { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} x this v w y this v x y u w u Tree Based Partial Orders
27
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} x this v w y this v x y u w u Tree Based Partial Orders nodes must be locked in tree order
28
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} x this v w y this v x y u w u nodes are locked in tree order Tree Based Partial Orders
29
Checking Tree Mutations A tree edge may be deleted A tree edge may be deleted A tree edge from x to y may be added iff A tree edge from x to y may be added iff y is a Root x is not in Tree(y) For onstage nodes x & y, analysis tracks For onstage nodes x & y, analysis tracks If y is a Root If x is not in Tree(y) If x has a tree edge to y Lightweight shape analysis Lightweight shape analysis
30
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} Checking Tree Mutations x this v w y this v x y u w u
31
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} Checking Tree Mutations this v x y u w x = this.right v = x.left w = v.right
32
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} Checking Tree Mutations this v x y u w x = this.right v = x.left w is Root v not in Tree(w) x not in Tree(w) this not in Tree(w)
33
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} Checking Tree Mutations this w x y x = this.right w = x.left v is Root x not in Tree(v) w not in Tree(v) this not in Tree(v) v u
34
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} Checking Tree Mutations this u v x v = this.right w = x.left x is Root this not in Tree(x) v not in Tree(x) w y
35
class Node self : l { tree Node self : l left; tree Node self : l left; tree Node self : l right; tree Node self : l right; synchronized void rotateRight() locks (this) { synchronized void rotateRight() locks (this) { Node x = this.right; synchronized (x) { Node x = this.right; synchronized (x) { Node v = x.left; synchronized (v) { Node v = x.left; synchronized (v) { Node w = v.right; Node w = v.right; v.right = null; v.right = null; x.left = w; x.left = w; this.right = v; this.right = v; v.right = x; v.right = x; }}} }}}} Checking Tree Mutations this u v x v = this.right w = x.left x = v.right w y
36
Type System Preventing data races Preventing data races Preventing deadlocks using Preventing deadlocks using Static lock levels Recursive data structures Mutable trees Monotonic DAGs Runtime ordering
37
DAG Based Partial Orders Locks in a level can be DAG-ordered Locks in a level can be DAG-ordered DAGs cannot be arbitrarily modified DAGs cannot be arbitrarily modified DAGs can be built bottom-up by DAGs can be built bottom-up by Allocating a new node Initializing its DAG fields class Node self : l { dag Node self : l left; dag Node self : l left; dag Node self : l right; dag Node self : l right; …}
38
Type System Preventing data races Preventing data races Preventing deadlocks using Preventing deadlocks using Static lock levels Recursive data structures Mutable trees Monotonic DAGs Runtime ordering
39
class Account { int balance = 0; int balance = 0; void deposit(int x) { balance += x; } void deposit(int x) { balance += x; } void withdraw(int x) { balance -= x; } void withdraw(int x) { balance -= x; }} void transfer(Account a1, Account a2, int x) { synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); } synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); }} Runtime Ordering of Locks
40
class Account implements Dynamic { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; } void withdraw(int x) requires (this) { balance -= x; } void withdraw(int x) requires (this) { balance -= x; }} void transfer(Account self : v a1, Account self : v a2, int x) locks(v) { synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); } synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); }} Runtime Ordering of Locks
41
class Account implements Dynamic { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; } void withdraw(int x) requires (this) { balance -= x; } void withdraw(int x) requires (this) { balance -= x; }} void transfer(Account self : v a1, Account self : v a2, int x) locks(v) { synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); } synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); }} Runtime Ordering of Locks Account objects are dynamically ordered
42
class Account implements Dynamic { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; } void withdraw(int x) requires (this) { balance -= x; } void withdraw(int x) requires (this) { balance -= x; }} void transfer(Account self : v a1, Account self : v a2, int x) locks(v) { synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); } synchronized (a1, a2) in { a1.withdraw(x); a2.deposit(x); }} Runtime Ordering of Locks locks are acquired in runtime order
43
Reducing Programming Overhead Type inference and default types significantly reduce programming overhead Type inference and default types significantly reduce programming overhead Single threaded programs need no annotations Single threaded programs need no annotations Our approach supports separate compilation Our approach supports separate compilation
44
Experience
45
Multithreaded Server Programs Program Lines of code Lines changed elevator52315 http server 56326 chat server 30822 stock quote server 24212 game server 87 8711 phone (database) server 30210
46
Java Libraries Program Lines of code Lines changed java.util.Hashtable101153java.util.HashMap 852 85246 java.util.Vector 992 99235 java.util.ArrayList 533 53318
47
Java Libraries Program Lines of code Lines changed java.io.OutputStream134 3java.io.BufferedWriter253 9 java.io.OutputStreamWriter26611 java.io.Writer177 6 java.io.PrintStream56814java.io.FilterOutputStream148 5
48
Related Work
49
Static tools Static tools Korty ( USENIX ’89 ) Sterling ( USENIX ’93 ) Detlefs, Leino, Nelson, Saxe ( SRC ’98 ) Engler, Chen, Hallem, Chou, Chelf ( SOSP ’01 ) Dynamic tools Dynamic tools Steele ( POPL ’90 ) Dinning, Schonberg ( PPoPP ’90 ) Savage,Burrows,Nelson,Sobalvarro,Anderson ( SOSP ’97 ) Praun, Gross ( OOPSLA ’01 ) Choi,Lee,Loginov,O’Callahan,Sarkar,Sridharan ( PLDI ’02 ) Related Work
50
Type systems Type systems Flanagan, Freund ( PLDI ’00 ) Bacon, Strom, Tarafdar ( OOPSLA ’00 ) Related Work
51
Ownership types Ownership types Clarke, Potter, Noble ( OOPSLA ’98 ), ( ECOOP ’01 ) Clarke, Drossopoulou ( OOPSLA ’02 ) Aldrich, Kostadinov, Chambers ( OOPSLA ’02 ) Boyapati, Rinard ( OOPSLA ’01 ) Boyapati, Lee, Rinard ( OOPSLA ’02 ) Boyapati, Liskov, Shrira ( MIT ’02 ) Boyapati, Salcianu, Beebee, Rinard ( MIT ’02 ) Related Work
52
Ownership Types We have used ownership types for We have used ownership types for Object encapsulation Constraining heap aliasing Modular effects clauses with subtyping Preventing data races and deadlocks Safe lazy upgrades in OODBs Safe region-based memory management Ownership types can serve as a foundation for future OO languages Ownership types can serve as a foundation for future OO languages
53
Conclusions Data races and deadlocks make multithreaded programming difficult Data races and deadlocks make multithreaded programming difficult We presented a static type system that prevents data races and deadlocks We presented a static type system that prevents data races and deadlocks Our type system is expressive Our type system is expressive Programs can be efficient and reliable Programs can be efficient and reliable
54
Ownership Types for Safe Programming: Preventing Data Races and Deadlocks Chandrasekhar Boyapati Robert Lee Martin Rinard Laboratory for Computer Science Massachusetts Institute of Technology {chandra, rhlee, rinard}@lcs.mit.edu
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.