Download presentation
Presentation is loading. Please wait.
1
Chandrasekhar Boyapati Laboratory for Computer Science Massachusetts Institute of Technology Ownership Types for Safe Programming
2
Motivation Making software reliable Is important Is important Role in civil infrastructure Effect on economy Is challenging because of complexity Is challenging because of complexity
3
This Talk Type system to increase software reliability Statically prevents many classes of errors Statically prevents many classes of errors Prevents data races and deadlocks Prevents representation exposure Enables region-based memory management Enables upgrades in persistent object stores Checking is fast and scalable Checking is fast and scalable Requires little programming overhead Requires little programming overhead Promising way for increasing reliability Promising way for increasing reliability
4
Outline Preventing data races Preventing data races Preventing deadlocks Preventing deadlocks Type inference Type inference Experience Experience Preventing other errors Preventing other errors
5
Preventing Data Races
6
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
7
Why Data Races are a Problem Some correct programs contain data races Some correct programs contain data races But most races are programming errors But most races are programming errors Code intended to execute atomically Synchronization omitted by mistake Consequences can be severe Consequences can be severe Nondeterministic timing-dependent bugs Difficult to detect, reproduce, eliminate
8
Avoiding Data Races Thread 1: x = x + 1; Thread 2: x = x + 2;
9
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);
10
Avoiding Data Races Problem: Locking is not enforced! Inadvertent programming errors… Thread 1: lock(l); x = x + 1; unlock(l); Thread 2: lock(l); x = x + 2; unlock(l);
11
Our Solution Type system for object-oriented languages Type system for object-oriented languages Statically prevents data races Statically prevents data races
12
Our Solution Type system for object-oriented languages Type system for object-oriented languages Statically prevents data races Statically prevents data races Programmers specify Programmers specify How each object is protected from races In types of variables pointing to objects Type checker statically verifies Type checker statically verifies Objects are used only as specified
13
Protection Mechanism of an Object Specifies the lock protecting the object, or Specifies the lock protecting the object, or Specifies object needs no locks because Specifies object needs no locks because Object is immutable Object is thread-local Object has a unique pointer
14
Protection Mechanism of an Object Specifies the lock protecting the object, or Specifies the lock protecting the object, or Specifies object needs no locks because Specifies object needs no locks because Object is immutable Object is thread-local Object has a unique pointer
15
Preventing Data Races class Account { int balance = 0; int balance = 0; void deposit(int x) { balance += x; } void deposit(int x) { balance += x; }} Account a1 = new Account(); fork { synchronized (a1) { a1.deposit(10); } }; Account a2 = new Account(); a2.deposit(10);
16
class Account { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; }} Account self a1 = new Account(); fork { synchronized (a1) { a1.deposit(10); } }; Account thisThread a2 = new Account(); a2.deposit(10); Preventing Data Races
17
class Account { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; }} Account self a1 = new Account(); fork { synchronized (a1) { a1.deposit(10); } }; Account thisThread a2 = new Account(); a2.deposit(10); a1 is protected by its own lock a2 is thread-local Preventing Data Races
18
class Account { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; }} Account self a1 = new Account(); fork { synchronized (a1) { a1.deposit(10); } }; Account thisThread a2 = new Account(); a2.deposit(10); deposit requires lock on “this” Preventing Data Races
19
class Account { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; }} Account self a1 = new Account(); fork { synchronized (a1) { a1.deposit(10); } }; Account thisThread a2 = new Account(); a2.deposit(10); a1 is locked before calling deposit a2 need not be locked Preventing Data Races
20
Types Impose No Dynamic Overhead Typechecker Translator (Removes extra types) Compiler JVM Java bytecodes + Extra types types Java
21
class Account { int balance = 0; int balance = 0; void deposit(int x) requires (this) { balance += x; } void deposit(int x) requires (this) { balance += x; }} Account self a1 = new Account(); fork { synchronized (a1) { a1.deposit(10); } }; Account thisThread a2 = new Account(); a2.deposit(10); Preventing Data Races
22
class Account { int balance = 0; int balance = 0; void deposit(int x) { balance += x; } void deposit(int x) { balance += x; }} Account a1 = new Account(); fork { synchronized (a1) { a1.deposit(10); } }; Account a2 = new Account(); a2.deposit(10);
23
Object Ownership
24
Every object is owned by Every object is owned by Itself, or Another object, or Special per-thread owner called thisThread Ownership relation forms a forest of trees Ownership relation forms a forest of trees thisThreadthisThread Thread2 objects Thread1 objects Potentially shared objects
25
Object Ownership Objects with a thisThread as their root owner Objects with a thisThread as their root owner Are local to the corresponding thread Objects with an object as their root owner Objects with an object as their root owner Are potentially shared between threads thisThreadthisThread Thread2 objects Thread1 objects Potentially shared objects
26
Object Ownership Every object is protected by its root owner Every object is protected by its root owner For race-free access to an object For race-free access to an object A thread must lock its root owner A thread implicitly holds lock on its thisThread A thread implicitly holds lock on its thisThread thisThreadthisThread Thread2 objects Thread1 objects Potentially shared objects
27
class TStack { TNode head; TNode head; void push(T value) {…} void push(T value) {…} T pop() {…} T pop() {…}} class TNode { TNode next; TNode next; T value; T value; …} class T {…} TStack Example value next head value next value next … … … TStack TNode T
28
class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack TNode T
29
TStack Example Classes are parameterized with owners First owner owns the “this” object class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack TNode T
30
TStack Example TStack owns the head TNode class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack TNode T
31
TStack Example All TNodes have the same owner class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack TNode T
32
TStack Example s1 is a thread-local stack with thread-local elements class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack thisThread, thisThread s1; TStack thisThread, self s2; TStack self, self s3; thisThread TStack TNode T
33
TStack Example s2 is a thread-local stack with shared elements class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack thisThread, thisThread s1; TStack thisThread, self s2; TStack self, self s3; thisThread TStack TNode T
34
TStack Example s3 is a shared stack with shared elements class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack thisThread, thisThread s1; TStack thisThread, self s2; TStack self, self s3; TStack TNode T
35
class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …} TStack Example Methods can require callers to hold locks on root owners
36
class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …} Type Checking Pop Method value next head value next value next … … … TStack TNode T
37
Locks held thisThread,RootOwner(this) class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …}
38
Type Checking Pop Method Locks held thisThread,RootOwner(this) Locks required RootOwner(this) class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …}
39
Type Checking Pop Method Locks held thisThread,RootOwner(this) Locks required ? class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …}
40
Type Checking Pop Method Locks held thisThread,RootOwner(this) Locks required RootOwner(head) class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …}
41
Type Checking Pop Method Locks held thisThread,RootOwner(this) Locks required RootOwner(head) = RootOwner(this) class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …}
42
Type Checking Pop Method Locks held thisThread,RootOwner(this) Locks required RootOwner(this),RootOwner(head) = RootOwner(this) class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …}
43
Type Checking Pop Method class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; … T TOwner pop() requires (this) { T TOwner pop() requires (this) { if (head == null) return null; if (head == null) return null; T TOwner value = head.value(); T TOwner value = head.value(); head = head.next(); head = head.next(); return value; return value; }} class TNode nodeOwner, TOwner { T TOwner value() requires (this) {…} T TOwner value() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} TNode nodeOwner, TOwner next() requires (this) {…} …}
44
Preventing Data Races Data races make programming difficult Data races make programming difficult Our type system prevents data races Our type system prevents data races Programmers specify Programmers specify How each object is protected from races Type checker statically verifies Type checker statically verifies Objects are used only as specified
45
Other Benefits of Race-free Types
46
Data races expose the effects of Data races expose the effects of Weak memory consistency models Standard compiler optimizations
47
What is the value of z? Thread 1: y=0; y=0; x=1; x=1; Thread 2: z=x+y; Initially: x=0; x=0; y=1; y=1;
48
What is the value of z? Thread 1: y=0; y=0; x=1; x=1; Thread 2: z=x+y; Initially: x=0; x=0; y=1; y=1; z=x+y;y=0;x=1;z=1y=0;z=x+y;x=1;z=0y=0;x=1;z=x+y;z=1 Possible Interleavings
49
What is the value of z? Thread 1: y=0; y=0; x=1; x=1; Thread 2: z=x+y; Initially: x=0; x=0; y=1; y=1; z=x+y;y=0;x=1;z=1y=0;z=x+y;x=1;z=0y=0;x=1;z=x+y;z=1 x=1;z=x+y;y=0;z=2 !!! Possible Interleavings Above instruction reordering legal in single-threaded programs Violates sequential consistency in multithreaded programs
50
Weak Memory Consistency Models Are complicated in presence of data races Are complicated in presence of data races Original Java memory model was Original Java memory model was Ambiguous and buggy Formal semantics still under development Formal semantics still under development Manson, Pugh ( Java Grande/ISCOPE ’01 ) Maessen, Arvind, Shen ( OOPSLA ’00 )
51
Other Benefits of Race-free Types Data races expose effects of Data races expose effects of Weak memory consistency models Standard compiler optimizations Races complicate program analysis Races complicate program analysis Races complicate human understanding Races complicate human understanding Race-free languages Race-free languages Eliminate these issues Make multithreaded programming tractable
52
Outline Preventing data races Preventing data races Preventing deadlocks Preventing deadlocks Type inference Type inference Experience Experience Preventing other errors Preventing other errors
53
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
54
Avoiding Deadlocks Thread 1 Thread 2 Thread n … Lock 1 Lock n Lock 2 Lock 3
55
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 ~ ~
56
Avoiding Deadlocks Thread 1 Thread 2 Thread n … Lock 1 Lock n Lock 2 Lock 3 ~ ~ Problem: Lock ordering is not enforced! Inadvertent programming errors…
57
Our Solution Static type system that prevents deadlocks Static type system that prevents deadlocks Programmers specify Programmers specify Partial order among locks Type checker statically verifies Type checker statically verifies Locks are acquired in descending order Specified order is a partial order
58
Preventing Deadlocks Programmers specify lock ordering using Programmers specify lock ordering using Locks levels Recursive data structures Tree-based data structures DAG-based data structures Runtime ordering
59
Lock Level Based Partial Orders Locks belong to lock levels Locks belong to lock levels Lock levels are partially ordered Lock levels are partially ordered Threads must acquire locks in order Threads must acquire locks in order
60
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; }}} }}}}
61
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
62
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
63
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
64
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
65
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
66
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
67
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
68
Preventing Deadlocks Programmers specify lock ordering using Programmers specify lock ordering using Locks levels Recursive data structures Tree-based data structures DAG-based data structures Runtime ordering
69
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 or sibling pointers Threaded trees…
70
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
71
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
72
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
73
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 flow sensitive analysis checks that tree order is preserved Tree Based Partial Orders
74
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
75
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
76
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
77
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)
78
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
79
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
80
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
81
Preventing Deadlocks Programmers specify lock ordering using Programmers specify lock ordering using Locks levels Recursive data structures Tree-based data structures DAG-based data structures Runtime ordering
82
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; …}
83
Preventing Deadlocks Programmers specify lock ordering using Programmers specify lock ordering using Locks levels Recursive data structures Tree-based data structures DAG-based data structures Runtime ordering
84
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
85
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
86
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
87
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
88
Preventing Deadlocks Static type system that prevents deadlocks Static type system that prevents deadlocks Programmers specify Programmers specify Partial order among locks Type checker statically verifies Type checker statically verifies Locks are acquired in descending order Specified order is a partial order
89
Reducing Programming Overhead
90
class A oa1, oa2 {…} class B ob1, ob2, ob3 extends A ob1, ob3 {…} class C { void m(B this, oc1, thisThread b) { void m(B this, oc1, thisThread b) { A a1; A a1; B b1; B b1; b1 = b; b1 = b; a1 = b1; a1 = b1; }} Inferring Owners of Local Variables
91
class A oa1, oa2 {…} class B ob1, ob2, ob3 extends A ob1, ob3 {…} class C { void m(B this, oc1, thisThread b) { void m(B this, oc1, thisThread b) { A x1, x2 a1; A x1, x2 a1; B x3, x4, x5 b1; B x3, x4, x5 b1; b1 = b; b1 = b; a1 = b1; a1 = b1; }} Inferring Owners of Local Variables Augment unknown types with owners
92
class A oa1, oa2 {…} class B ob1, ob2, ob3 extends A ob1, ob3 {…} class C { void m(B this, oc1, thisThread b) { void m(B this, oc1, thisThread b) { A x1, x2 a1; A x1, x2 a1; B x3, x4, x5 b1; B x3, x4, x5 b1; b1 = b; b1 = b; a1 = b1; a1 = b1; }} Inferring Owners of Local Variables Gather constraints x3 = this x4 = oc1 x5 = thisThread
93
class A oa1, oa2 {…} class B ob1, ob2, ob3 extends A ob1, ob3 {…} class C { void m(B this, oc1, thisThread b) { void m(B this, oc1, thisThread b) { A x1, x2 a1; A x1, x2 a1; B x3, x4, x5 b1; B x3, x4, x5 b1; b1 = b; b1 = b; a1 = b1; a1 = b1; }} Inferring Owners of Local Variables Gather constraints x3 = this x4 = oc1 x5 = thisThread x1 = x3 x2 = x5
94
class A oa1, oa2 {…} class B ob1, ob2, ob3 extends A ob1, ob3 {…} class C { void m(B this, oc1, thisThread b) { void m(B this, oc1, thisThread b) { A this, thisThread a1; A this, thisThread a1; B this, oc1, thisThread b1; B this, oc1, thisThread b1; b1 = b; b1 = b; a1 = b1; a1 = b1; }} Inferring Owners of Local Variables Solve constraints x3 = this x4 = oc1 x5 = thisThread x1 = x3 x2 = x5
95
class A oa1, oa2 {…} class B ob1, ob2, ob3 extends A ob1, ob3 {…} class C { void m(B this, oc1, thisThread b) { void m(B this, oc1, thisThread b) { A this, thisThread a1; A this, thisThread a1; B this, oc1, thisThread b1; B this, oc1, thisThread b1; b1 = b; b1 = b; a1 = b1; a1 = b1; }} Inferring Owners of Local Variables Solve constraints x3 = this x4 = oc1 x5 = thisThread x1 = x3 x2 = x5 Only equality constraints between ownersOnly equality constraints between owners Takes almost linear time to solveTakes almost linear time to solve
96
Reducing Programming Overhead Type inference for method local variables Type inference for method local variables Default types for method signatures & fields Default types for method signatures & fields User defined defaults as well User defined defaults as well Significantly reduces programming overhead Significantly reduces programming overhead Approach supports separate compilation Approach supports separate compilation
97
Experience
98
Multithreaded Server Programs Program # Lines of code # Lines annotated SMTP Server (Apache) 210546 POP3 Mail Server (Apache) 136431 Discrete Event Simulator (ETH Zurich) 523 52315 HTTP Server 563 56326 Chat Server 308 30822 Stock Quote Server 242 24212 Game Server 87 8711 Database Server 302 30210
99
Java Libraries Program # Lines of code # Lines annotated java.util.Hashtable101153 java.util.HashMap 852 85246 java.util.Vector 992 99235 java.util.ArrayList 533 53318 java.io.PrintStream 568 56814 java.io.FilterOutputStream 148 148 5 java.io.BufferedWriter 253 253 9 java.io.OutputStreamWriter 266 26611
100
Java Libraries Java has two classes for resizable arrays Java has two classes for resizable arrays java.util.Vector Self synchronized, do not create races Always incur synchronization overhead java.util.ArrayList No unnecessary synchronization overhead Could be used unsafely to create races We provide generic resizable arrays We provide generic resizable arrays Safe, but no unnecessary overhead Programs can be both reliable and efficient Programs can be both reliable and efficient
101
Prevent data races and deadlocks Prevent data races and deadlocks Boyapati, Rinard ( OOPSLA ’01 ) Boyapati, Lee, Rinard ( OOPSLA ’02 ) Prevent representation exposure Prevent representation exposure Boyapati, Liskov, Shrira ( POPL ’03 ) Enable safe region-based memory management Enable safe region-based memory management Boyapati, Salcianu, Beebee, Rinard ( PLDI ’03 ) Enable safe upgrades in persistent object stores Enable safe upgrades in persistent object stores Boyapati, Liskov, Shrira, Moh, Richman ( OOPSLA ’03 ) Ownership Types
102
Goal is local reasoning about correctness Goal is local reasoning about correctness Prove a class meets its specification, using specifications but not code of other classes Crucial when dealing with large programs Crucial when dealing with large programs Requires no interference from outside Requires no interference from outside Internal sub-objects must be encapsulated Preventing Representation Exposure
103
Say Stack s is implemented with linked list Say Stack s is implemented with linked list Outside objects must not access list nodes Outside objects must not access list nodes Preventing Representation Exposure s o ~ ~
104
Say Stack s is implemented with linked list Say Stack s is implemented with linked list Outside objects must not access list nodes Outside objects must not access list nodes Preventing Representation Exposure Program can declare s owns list nodes Program can declare s owns list nodes System ensures list is encapsulated in s System ensures list is encapsulated in s s o ~ ~
105
class TStack { TNode head; TNode head; void push(T value) {…} void push(T value) {…} T pop() {…} T pop() {…}} class TNode { TNode next; TNode next; T value; T value; …} class T {…} value next head value next value next … … … TStack TNode T Preventing Representation Exposure
106
class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack TNode T Preventing Representation Exposure
107
TNode objects are encapsulated in TStack object class TStack stackOwner, TOwner { TNode this, TOwner head; TNode this, TOwner head; …} class TNode nodeOwner, TOwner { TNode nodeOwner, TOwner next; TNode nodeOwner, TOwner next; T TOwner value; T TOwner value; …} TStack TNode T Preventing Representation Exposure
108
thisThreadthisThread Thread2 objects Thread1 objects Potentially shared objects Preventing Representation Exposure
109
thisThreadthisThread Thread2 objects Thread1 objects Potentially shared objects Preventing Representation Exposure
110
class IntVector { int size () {…} … int size () {…} …} class IntStack { void push (int x) {…} … void push (int x) {…} …} void m (IntStack s, IntVector v) { int n = v.size(); s.push(3); assert( n == v.size() ); int n = v.size(); s.push(3); assert( n == v.size() );} Example of Local Reasoning Is the condition in the assert true?
111
class IntVector { int size () reads (this) {…} … int size () reads (this) {…} …} class IntStack { void push (int x) writes (this) {…} … void push (int x) writes (this) {…} …} void m (IntStack s, IntVector v) where !(v <= s) !(s <= v) { int n = v.size(); s.push(3); assert( n == v.size() ); int n = v.size(); s.push(3); assert( n == v.size() );} Example of Local Reasoning Is the condition in the assert true?
112
class IntVector { int size () reads (this) {…} … int size () reads (this) {…} …} class IntStack { void push (int x) writes (this) {…} … void push (int x) writes (this) {…} …} void m (IntStack s, IntVector v) where !(v <= s) !(s <= v) { int n = v.size(); s.push(3); assert( n == v.size() ); int n = v.size(); s.push(3); assert( n == v.size() );} Example of Local Reasoning size only reads v and its encapsulated objects push only writes s and its encapsulated objects
113
class IntVector { int size () reads (this) {…} … int size () reads (this) {…} …} class IntStack { void push (int x) writes (this) {…} … void push (int x) writes (this) {…} …} void m (IntStack s, IntVector v) where !(v <= s) !(s <= v) { int n = v.size(); s.push(3); assert( n == v.size() ); int n = v.size(); s.push(3); assert( n == v.size() );} Example of Local Reasoning s is not encapsulated in v, and v is not encapsulated in s
114
class IntVector { int size () reads (this) {…} … int size () reads (this) {…} …} class IntStack { void push (int x) writes (this) {…} … void push (int x) writes (this) {…} …} void m (IntStack s, IntVector v) where !(v <= s) !(s <= v) { int n = v.size(); s.push(3); assert( n == v.size() ); int n = v.size(); s.push(3); assert( n == v.size() );} Example of Local Reasoning So size and push cannot interfere So the condition in the assert must be true
115
Prevent data races and deadlocks Prevent data races and deadlocks Boyapati, Rinard ( OOPSLA ’01 ) Boyapati, Lee, Rinard ( OOPSLA ’02 ) Prevent representation exposure Prevent representation exposure Boyapati, Liskov, Shrira ( POPL ’03 ) Enable safe region-based memory management Enable safe region-based memory management Boyapati, Salcianu, Beebee, Rinard ( PLDI ’03 ) Enable safe upgrades in persistent object stores Enable safe upgrades in persistent object stores Boyapati, Liskov, Shrira, Moh, Richman ( OOPSLA ’03 ) Ownership Types
116
Related Work
117
Static tools for preventing races and deadlocks Static tools for preventing races and deadlocks Korty ( USENIX ’89 ) Sterling ( USENIX ’93 ) Detlefs, Leino, Nelson, Saxe ( SRC ’98 ) Engler, Chen, Hallem, Chou, Chelf ( SOSP ’01 ) Dynamic tools for preventing races and deadlocks Dynamic tools for preventing races and deadlocks Steele ( POPL ’90 ) Dinning, Schonberg ( PPoPP ’90 ) Savage, Burrows, Nelson, Sobalvarro, Anderson ( SOSP ’97 ) Cheng, Feng, Leiserson, Randall, Stark ( SPAA ‘98 ) Praun, Gross ( OOPSLA ’01 ) Choi, Lee, Loginov, O’Callahan, Sarkar, Sridharan ( PLDI ’02 ) Related Work Useful but unsound
118
Related Work Types for preventing data races Types for preventing data races Flanagan, Freund ( PLDI ’00 ) Bacon, Strom, Tarafdar ( OOPSLA ’00 )
119
Related Work Types for preventing data races Types for preventing data races Flanagan, Freund ( PLDI ’00 ) Bacon, Strom, Tarafdar ( OOPSLA ’00 ) Types for preventing representation exposure Types for preventing representation exposure Clarke, Potter, Noble ( OOPSLA ’98 ), ( ECOOP ’01 ) Clarke, Drossopoulou ( OOPSLA ’02 ) Aldrich, Kostadinov, Chambers ( OOPSLA ’02 )
120
Related Work Types for preventing data races Types for preventing data races Flanagan, Freund ( PLDI ’00 ) Bacon, Strom, Tarafdar ( OOPSLA ’00 ) Types for preventing representation exposure Types for preventing representation exposure Clarke, Potter, Noble ( OOPSLA ’98 ), ( ECOOP ’01 ) Clarke, Drossopoulou ( OOPSLA ’02 ) Aldrich, Kostadinov, Chambers ( OOPSLA ’02 ) Types for region-based memory management Types for region-based memory management Tofte, Talpin ( POPL ’94 ) Christiansen, Henglein, Niss, Velschow ( DIKU ’98 ) Crary, Walker, Morrisett ( POPL ’99 ) Grossman, Morrisett, Jim, Hicks, Wang, Cheney ( PLDI ’02 )
121
Related Work Our work unifies these areas Types for preventing data races Types for preventing data races Flanagan, Freund ( PLDI ’00 ) Bacon, Strom, Tarafdar ( OOPSLA ’00 ) Types for preventing representation exposure Types for preventing representation exposure Clarke, Potter, Noble ( OOPSLA ’98 ), ( ECOOP ’01 ) Clarke, Drossopoulou ( OOPSLA ’02 ) Aldrich, Kostadinov, Chambers ( OOPSLA ’02 ) Types for region-based memory management Types for region-based memory management Tofte, Talpin ( POPL ’94 ) Christiansen, Henglein, Niss, Velschow ( DIKU ’98 ) Crary, Walker, Morrisett ( POPL ’99 ) Grossman, Morrisett, Jim, Hicks, Wang, Cheney ( PLDI ’02 )
122
Conclusions Ownership types for object-oriented programs Statically prevent several classes of errors Statically prevent several classes of errors Prevent data races and deadlocks Prevent representation exposure Enable region-based memory management Enable upgrades in persistent object stores Provide documentation that lives with code Provide documentation that lives with code Require little programming overhead Require little programming overhead Promising way to make programs reliable Promising way to make programs reliable
123
Chandrasekhar Boyapati Laboratory for Computer Science Massachusetts Institute of Technology Ownership Types for Safe Programming
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.