Subsystems: Improved exception handling for
Java (DRAFT)
Bart Jacobs, Frank Piessens
Outline
Try-Catch: Problem + Solution Locking: Problem + Solution Cancellation: Problem + Solution Cleaning Up: Problem + Solution
Outline
Try-Catch: Problem + Solution Locking: Problem + Solution Cancellation: Problem + Solution Cleaning Up: Problem + Solution
Problem Statement:A Typical Try-Catch Patternclass Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } } …}
This program is broken!
Why?
Problem Statement:An OutOfMemoryError…class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } } …}
m.count == 11
m.widgets.length == 10
Problem Statement:An OutOfMemoryError…class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } } …}
1
2
3
4
m.count == 11
m.widgets.length == 10
Problem Statement:An OutOfMemoryError…class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } } …}
1
2
3
4
5
m.count == 12
m.widgets.length == 10
Problem Statement:A Typical Try-Catch Patternclass Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } } …}
class WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10]; Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } void removeWidget(Widget w) { … }}
Proposed Solution:Re-entering the Owner Subsystem
class Program { public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } } …}
root subsyste
m
child subsyste
m
class Program { public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } } …}
class WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10]; Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } void removeWidget(Widget w) { … }}
Proposed Solution:Re-entering an Outer Subsystem
Subsystems: Basic Operation
class Program { public static void main(String[] args) { Subsystem s = Subsystem.getCurrent(); try { reenter (s) { throw new RuntimeException(); } } catch (Throwable t) { System.out.println(t + “ was caught.”); } }}
Not caught
Subsystems: Basic Operationclass Program { public static void main(String[] args) { try { Subsystem s = Subsystem.getCurrent(); try { reenter (s) { throw new RuntimeException(); } } catch (Throwable t) { System.out.println(t + “ caught by inner.”); } } catch (Throwable t) { System.out.println(t + “ caught by outer.”); } }}
Caught by outer
Subsystems: Implementationclass Subsystem { static Stack<Subsystem> stack = new Stack<Subsystem>(); static { stack.push(new Subsystem(); } Throwable exception; Subsystem parent; List<Subsystem> children = new ArrayList<Subsystem>(); static Subsystem getCurrent() { return stack.peek(); } static void enterNew() { Subsystem s = new Subsystem(); s.parent = getCurrent(); getCurrent().children.add(s); stack.push(s); } static void reenter(Subsystem s) { s.checkNotFailed(); stack.push(s); } static void exit(Throwable e) { if (e != null) getCurrent().setFailed(e); stack.pop(); getCurrent().checkNotFailed(); } void setFailed(Throwable e) { exception = e; for (Subsystem c : children) c.setFailed(e); } void checkNotFailed() { if (exception != null) throw exception; } static void finish(Throwable e) { getCurrent().parent.children.remove(getCurrent()); exit(e); }}
Expansion [[ try { S } catch (Throwable e) { S’ } ]] = Subsystem.enterNew(); Throwable t = null; try { S } catch (Throwable e) { t = e; } Subsystem.finish(t); if (t != null) { Throwable e = t; S’ }
Expansion [[ reenter (s) { S } ]] = Subsystem.reenter(s); Throwable t = null; try { S } catch (Throwable e) { t = e; } Subsystem.exit(t); if (t != null) { throw t; }
Outline
Try-Catch: Problem + Solution Locking: Problem + Solution Cancellation: Problem + Solution Cleaning Up: Problem + Solution
Problem Statement:A Typical Locking Patternclass Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
synchronized Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
synchronized void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
This program is broken!
Why?
Problem Statement:An OutOfMemoryError…class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
synchronized Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
synchronized void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
In thread 1
m.count == 11
m.widgets.length == 10
Problem Statement:An OutOfMemoryError…class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10];
synchronized Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
synchronized void removeWidget(Widget w) { … }}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
In thread 1
In thread 2
m.count == 12
m.widgets.length == 10
Subsystems To The Rescueclass WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10];
synchronized Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
Subsystems To The Rescueclass WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10];
synchronized Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
In thread 1
Subsystems To The Rescueclass WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10];
synchronized Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
In thread 1
In thread 2
Subsystems To The Rescueclass WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10];
synchronized Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
In thread 1
In thread 2
In main thread
Subsystems
When an exception occurs in a subsystem s For safety: New attempts to enter s (or a
descendant) rethrow the exception, and
For liveness: Computations executing in s (or a descendant) in other threads are stopped I.e.: In each thread that is executing in s (or a
descendant), an exception is thrown (using Thread.stop()), which is caught when leaving s
Outline
Try-Catch: Problem + Solution Locking: Problem + Solution Cancellation: Problem + Solution Cleaning Up: Problem + Solution
Problem Statement:A Typical Cancellation Patternclass Task { boolean cancel; Thread thread; synchronized void cancel() { cancel = true; if (thread != null) thread.stop(); } synchronized void setThread(Thread t) { thread = t; if (cancel) throw new ThreadDeath(); }}
class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10]; synchronized Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); List<Task> tasks = new ArrayList<Task>(); while (true) { String cmd = getUserCommand(); if (cmd.equals(“cancelAll”)) { for (Task t : tasks) t.cancel(); continue; } final Task task = new Task(); tasks.add(task); new Thread() { public void run() { try { task.setThread(Thread.currentThread()); … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
This program is broken!
Why?
Problem Statement:A ThreadDeath…class Task { boolean cancel; Thread thread; synchronized void cancel() { cancel = true; if (thread != null) thread.stop(); } synchronized void setThread(Thread t) { thread = t; if (cancel) throw new ThreadDeath(); }}
class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10]; synchronized Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); List<Task> tasks = new ArrayList<Task>(); while (true) { String cmd = getUserCommand(); if (cmd.equals(“cancelAll”)) { for (Task t : tasks) t.cancel(); continue; } final Task task = new Task(); tasks.add(task); new Thread() { public void run() { try { task.setThread(Thread.currentThread()); … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
ThreadDeath in thread 1
m.count == 11
m.widgets.length == 10
Problem Statement:A ThreadDeath…class Task { boolean cancel; Thread thread; synchronized void cancel() { cancel = true; if (thread != null) thread.stop(); } synchronized void setThread(Thread t) { thread = t; if (cancel) throw new ThreadDeath(); }}
class Widget { … }class WidgetManager { int count; Widget[] widgets = new Widget[10]; synchronized Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); List<Task> tasks = new ArrayList<Task>(); while (true) { String cmd = getUserCommand(); if (cmd.equals(“cancelAll”)) { for (Task t : tasks) t.cancel(); continue; } final Task task = new Task(); tasks.add(task); new Thread() { public void run() { try { task.setThread(Thread.currentThread()); … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
ThreadDeath in thread 1
m.count == 12
m.widgets.length == 10
In thread 2
Proposed Solution:Subsystem Cancellationclass Task { boolean cancel; Subsystem subsystem; synchronized void cancel() { cancel = true; if (subsystem != null) subsystem.cancel(); } synchronized void setSubsystem(Subsystem s) { subsystem = s; if (cancel) throw new ThreadDeath(); }}
class Widget { … }class WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10]; synchronized Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); List<Task> tasks = new ArrayList<Task>(); while (true) { String cmd = getUserCommand(); if (cmd.equals(“cancelAll”)) { for (Task t : tasks) t.cancel(); continue; } final Task task = new Task(); tasks.add(task); new Thread() { public void run() { try { task.setSubsystem(Subsystem.getCurrent()); … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
Proposed Solution:Subsystem Cancellationclass Task { boolean cancel; Subsystem subsystem; synchronized void cancel() { cancel = true; if (subsystem != null) subsystem.cancel(); } synchronized void setSubsystem(Subsystem s) { subsystem = s; if (cancel) throw new ThreadDeath(); }}
class Widget { … }class WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10]; synchronized Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } …}
class Program { public static void main(String[] args) { final WidgetManager m = new WidgetManager(); List<Task> tasks = new ArrayList<Task>(); while (true) { String cmd = getUserCommand(); if (cmd.equals(“cancelAll”)) { for (Task t : tasks) t.cancel(); continue; } final Task task = new Task(); tasks.add(task); new Thread() { public void run() { try { task.setSubsystem(Subsystem.getCurrent()); … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } …}
(Child subsystem is
cancelled)
ThreadDeath on entry to
child
Outline
Try-Catch: Problem + Solution Locking: Problem + Solution Cancellation: Problem + Solution Cleaning Up: Problem + Solution
Problem Statement:A Typical Cleanup Patternclass Widget implements Closeable { WidgetManager m; Widget(WidgetManager m) { this.m = m; } public void close() { m.removeWidget(this); } …}class WidgetManager { int count; Widget[] widgets = new Widget[10];
Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
void removeWidget(Widget w) { … }}
class Program { static Widget allocGreenWidget(WidgetManager m) { Widget w = m.allocWidget(); w.setColor(Color.green); return w; } public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … Widget w = allocGreenWidget(m); try { … } finally { w.close(); } … } catch (Throwable t) { showErrorMessage(t); } } } …} This program
is broken! Why?
Problem Statement:A StackOverflowError…class Widget implements Closeable { WidgetManager m; Widget(WidgetManager m) { this.m = m; } public void close() { m.removeWidget(this); } …}class WidgetManager { int count; Widget[] widgets = new Widget[10];
Widget allocWidget() { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; }
void removeWidget(Widget w) { … }}
class Program { static Widget allocGreenWidget(WidgetManager m) { Widget w = m.allocWidget(); w.setColor(Color.green); return w; } public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … Widget w = allocGreenWidget(m); try { … } finally { w.close(); } … } catch (Throwable t) { showErrorMessage(t); } } } …}
Proposed Solution:Subsystem Cleanup Routinesclass Widget implements Closeable { Subsystem client = Subsystem.getCaller(); WidgetManager m; Widget(WidgetManager m) { client.registerCleanup(this); this.m = m; } public void close() { m.removeWidget(this); } …}class WidgetManager { Subsystem s = Subsystem.getCurrent(); int count; Widget[] widgets = new Widget[10]; Widget allocWidget() { reenter (s) { Widget w = new Widget(); count++; if (count == widgets.length + 1) { Widget[] ws = new Widget[count * 2]; System.arraycopy(widgets, 0, ws, 0, widgets.length); widgets = ws; } widgets[count – 1] = w; return w; } } void removeWidget(Widget w) { reenter (s) { w.client.unregisterCleanup(w); … } }}
class Program { static Widget allocGreenWidget(WidgetManager m) { Widget w = m.allocWidget(); w.setColor(Color.green); return w; } public static void main(String[] args) { WidgetManager m = new WidgetManager(); while (true) { String cmd = getUserCommand(); try { … Widget w = allocGreenWidget(m); try { … } finally { w.close(); } … } catch (Throwable t) { showErrorMessage(t); } } } …}
class Subsystem { static void finish(Throwable t) { Subsystem s = getCurrent(); … while (!s.cleanupStack.isEmpty()) s.cleanupStack.peek().close(); }}
Related Work(Under Construction) Re-entering: Similar to performing a remote procedure call in
systems where each subsystem is a separate process/thread Cancellation:
Rudys, Wallach. Termination in language-based systems. ACM TISS 5(2), 2002.
Wick, Flatt. Memory accounting without partitions. ISMM 2004. Flatt, Findler. Kill-safe synchronization abstractions. PLDI 2004. Flatt, Findler, Krishnamurthi, Felleisen. Programming languages
as operating systems. ICFP 1999. Subsystem cleanup stacks:
Similar to compensation stacks in Weimer. Finding and preventing run-time error handling mistakes. OOPSLA 2004.
Conclusion It’s hard in Java to catch unchecked exceptions safely Many (most?) existing try-catch blocks for unchecked exceptions
are probably unsafe Many synchronized blocks are probably unsafe There is no easy and safe way to cancel a computation Subsystems make it easy to fix these problems for the example
programs Future work: Assess the severity of the problem and the
effectiveness of subsystems in large programs