350 likes | 483 Vues
This draft paper discusses enhanced exception handling practices in Java, addressing common issues faced by developers. It explores the problems and solutions related to try-catch blocks, synchronization and locking, task cancellation, and resource cleanup. Through a comprehensive analysis, we provide examples demonstrating typical pitfalls, such as OutOfMemoryErrors encountered in widget management. Each section aims to guide developers in implementing robust error handling strategies that improve application reliability and maintainability.
E N D
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
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); } } } … } Problem Statement:A Typical Try-Catch Pattern This program is broken! Why?
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); } } } … } Problem Statement:An OutOfMemoryError… m.count == 11 m.widgets.length == 10
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); } } } … } Problem Statement:An OutOfMemoryError… 3 4 1 2 m.count == 11 m.widgets.length == 10
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); } } } … } Problem Statement:An OutOfMemoryError… 3 4 1 2 5 m.count == 12 m.widgets.length == 10
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); } } } … } Problem Statement:A Typical Try-Catch Pattern
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) { … } } 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); } } } … } Proposed Solution:Re-entering the Owner Subsystem child subsystem root 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); } } } … } 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 Operation class 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
class 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 (Subsystemc : 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; } Subsystems: Implementation
Outline • Try-Catch: Problem + Solution • Locking: Problem + Solution • Cancellation: Problem + Solution • Cleaning Up: Problem + Solution
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; } synchronizedvoid 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(); } } … } Problem Statement:A Typical Locking Pattern This program is broken! Why?
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; } synchronizedvoid 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(); } } … } Problem Statement:An OutOfMemoryError… In thread 1 m.count == 11 m.widgets.length == 10
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; } synchronizedvoid 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(); } } … } Problem Statement:An OutOfMemoryError… In thread 1 In thread 2 m.count == 12 m.widgets.length == 10
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(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } … } Subsystems To The Rescue
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(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } … } Subsystems To The Rescue In thread 1
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(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } … } Subsystems To The Rescue In thread 2 In thread 1
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(); while (true) { String cmd = getUserCommand(); new Thread() { public void run() { try { … … m.allocWidget() … … … m.removeWidget(…) … … } catch (Throwable t) { showErrorMessage(t); } } }.start(); } } … } Subsystems To The Rescue In main thread In thread 2 In thread 1
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
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(); } } … } Problem Statement:A Typical Cancellation Pattern This program is broken! Why?
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(); } } … } Problem Statement:A ThreadDeath… ThreadDeath in thread 1 m.count == 11 m.widgets.length == 10
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(); } } … } Problem Statement:A ThreadDeath… ThreadDeath in thread 1 In thread 2 m.count == 12 m.widgets.length == 10
class 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 Cancellation
class 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 Cancellation (Child subsystem is cancelled) ThreadDeath on entry to child
Outline • Try-Catch: Problem + Solution • Locking: Problem + Solution • Cancellation: Problem + Solution • Cleaning Up: Problem + Solution
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); } } } … } Problem Statement:A Typical Cleanup Pattern This program is broken! Why?
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); } } } … } Problem Statement:A StackOverflowError…
class 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(); } } Proposed Solution:Subsystem Cleanup Routines
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