This patch improves the way runAsync calls its callbacks in two ways. First, if an exception happens while calling a callback, the exception is passed to GWT's global uncaught exception handler. Second, the callbacks happen in the order they are registered. This is done by updating the linked-list code that manages the list of pending runAsync callbacks. RunAsyncTest is updated to test for both of these. Review by: kprobst git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4697 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java index cfb0e54..72310d3 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
@@ -25,11 +25,11 @@ import com.google.gwt.dev.cfg.PublicOracle; import com.google.gwt.dev.cfg.StaticPropertyOracle; import com.google.gwt.dev.javac.CompilationState; +import com.google.gwt.dev.jdt.FindDeferredBindingSitesVisitor; import com.google.gwt.dev.shell.StandardGeneratorContext; import java.io.File; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; /** @@ -45,7 +45,10 @@ public static final String ASYNC_LOADER_CLASS_PREFIX = "AsyncLoader"; public static final String ASYNC_LOADER_PACKAGE = "com.google.gwt.lang.asyncloaders"; public static final String RUN_ASYNC_CALLBACK = "com.google.gwt.core.client.RunAsyncCallback"; + private static final String GWT_CLASS = FindDeferredBindingSitesVisitor.MAGIC_CLASS; private static final String PROP_RUN_ASYNC_NEVER_RUNS = "gwt.jjs.runAsyncNeverRuns"; + private static final String UNCAUGHT_EXCEPTION_HANDLER_CLASS = GWT_CLASS + + ".UncaughtExceptionHandler"; private final ArtifactSet artifactSet; private final CompilationState compilationState; @@ -110,12 +113,23 @@ } private void generateLoaderFields(PrintWriter srcWriter) { + srcWriter.println("// Whether the code for this entry point has loaded"); srcWriter.println("private static boolean loaded = false;"); + + srcWriter.println("// Whether the code for this entry point is currently loading"); srcWriter.println("private static boolean loading = false;"); + + srcWriter.println("// A callback caller for this entry point"); srcWriter.println("private static " + getLoaderSuperclassSimpleName() + " instance = new " + getLoaderSuperclassSimpleName() + "();"); + + srcWriter.println("// Callbacks that are pending"); srcWriter.println("private static " + getCallbackListSimpleName() - + " callbacks = null;"); + + " callbacksHead = null;"); + + srcWriter.println("// The tail of the callbacks list"); + srcWriter.println("private static " + getCallbackListSimpleName() + + " callbacksTail = null;"); } private void generateOnErrorMethod(PrintWriter srcWriter) { @@ -145,11 +159,19 @@ */ private void generateRunAsyncMethod(PrintWriter srcWriter) { srcWriter.println("public static void runAsync(RunAsyncCallback callback) {"); - srcWriter.println(getCallbackListSimpleName() + " newCallbackList = new " + srcWriter.println(getCallbackListSimpleName() + " newCallback = new " + getCallbackListSimpleName() + "();"); - srcWriter.println("newCallbackList.callback = callback;"); - srcWriter.println("newCallbackList.next = callbacks;"); - srcWriter.println("callbacks = newCallbackList;"); + srcWriter.println("newCallback.callback = callback;"); + + srcWriter.println("if (callbacksTail != null) {"); + srcWriter.println(" callbacksTail.next = newCallback;"); + srcWriter.println("}"); + + srcWriter.println("callbacksTail = newCallback;"); + srcWriter.println("if (callbacksHead == null) {"); + srcWriter.println(" callbacksHead = newCallback;"); + srcWriter.println("}"); + srcWriter.println("if (loaded) {"); srcWriter.println("instance.runCallbacks();"); srcWriter.println("return;"); @@ -163,29 +185,44 @@ private void generateRunCallbackOnFailuresMethod(PrintWriter srcWriter) { srcWriter.println("private static void runCallbackOnFailures(Throwable e) {"); - // TODO(spoon): this runs the callbacks in reverse order - srcWriter.println(getCallbackListSimpleName() + " callback = callbacks;"); - srcWriter.println("callbacks = null;"); - srcWriter.println("while (callback != null) {"); - srcWriter.println("callback.callback.onFailure(e);"); - srcWriter.println("callback = callback.next;"); + srcWriter.println("while (callbacksHead != null) {"); + srcWriter.println("callbacksHead.callback.onFailure(e);"); + srcWriter.println("callbacksHead = callbacksHead.next;"); srcWriter.println("}"); + srcWriter.println("callbacksTail = null;"); srcWriter.println("}"); } private void generateRunCallbacksMethod(PrintWriter srcWriter) { srcWriter.println("public void runCallbacks() {"); - // TODO(spoon): this runs the callbacks in reverse order - // TODO(spoon): this runs the callbacks immediately; deferred would be - // better - srcWriter.println(getCallbackListSimpleName() + " callback = callbacks;"); - srcWriter.println("callbacks = null;"); + + srcWriter.println("while (callbacksHead != null) {"); + + srcWriter.println(" " + UNCAUGHT_EXCEPTION_HANDLER_CLASS + " handler = " + + FindDeferredBindingSitesVisitor.MAGIC_CLASS + + ".getUncaughtExceptionHandler();"); + + srcWriter.println(" " + getCallbackListSimpleName() + " next = callbacksHead;"); + srcWriter.println(" callbacksHead = callbacksHead.next;"); + srcWriter.println(" if (callbacksHead == null) {"); + srcWriter.println(" callbacksTail = null;"); + srcWriter.println(" }"); + if (!Boolean.getBoolean(PROP_RUN_ASYNC_NEVER_RUNS)) { - srcWriter.println("while (callback != null) {"); - srcWriter.println("callback.callback.onSuccess();"); - srcWriter.println("callback = callback.next;"); - srcWriter.println("}"); + // TODO(spoon): this runs the callbacks immediately; deferred would be + // better + srcWriter.println(" if (handler == null) {"); + srcWriter.println(" next.callback.onSuccess();"); + srcWriter.println(" } else {"); + srcWriter.println(" try {"); + srcWriter.println(" next.callback.onSuccess();"); + srcWriter.println(" } catch (Throwable e) {"); + srcWriter.println(" handler.onUncaughtException(e);"); + srcWriter.println(" }"); + srcWriter.println(" }"); } + + srcWriter.println("}"); srcWriter.println("}"); } @@ -228,7 +265,7 @@ printWriter.println("package " + getPackage() + ";"); String[] imports = new String[] { RUN_ASYNC_CALLBACK, List.class.getCanonicalName(), - ArrayList.class.getCanonicalName(), ASYNC_FRAGMENT_LOADER}; + ASYNC_FRAGMENT_LOADER}; for (String imp : imports) { printWriter.println("import " + imp + ";"); } @@ -271,10 +308,10 @@ /** * Create a stand-in superclass of the actual loader. This is used to keep the - * liveness analyzer from thinking the real <code>runCallbacks()</code> - * method is available until <code>onLoad</code> has been called and the - * real loader instantiated. A little work on TypeTightener could prevent the - * need for this class. + * liveness analyzer from thinking the real <code>runCallbacks()</code> method + * is available until <code>onLoad</code> has been called and the real loader + * instantiated. A little work on TypeTightener could prevent the need for + * this class. */ private void writeLoaderSuperclass(TreeLogger logger, GeneratorContext ctx) throws UnableToCompleteException {
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java index 1184d9e..6062bd1 100644 --- a/user/src/com/google/gwt/core/client/GWT.java +++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -188,10 +188,20 @@ */ public static void runAsync(RunAsyncCallback callback) { /* - * By default, just call the callback. This allows using <code>runAsync</code> - * in code that might or might not run in a web browser. + * By default, just call the callback. This allows using + * <code>runAsync</code> in code that might or might not run in a web + * browser. */ - callback.onSuccess(); + UncaughtExceptionHandler handler = sUncaughtExceptionHandler; + if (handler == null) { + callback.onSuccess(); + } else { + try { + callback.onSuccess(); + } catch (Throwable e) { + handler.onUncaughtException(e); + } + } } /**
diff --git a/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java b/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java index ad4a702..e3a49e0 100644 --- a/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
@@ -17,23 +17,24 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; +import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; import com.google.gwt.junit.client.GWTTestCase; /** * Tests runAsync in various ways. */ public class RunAsyncTest extends GWTTestCase { + private static final String HELLO = "hello"; + private static final int RUNASYNC_TIMEOUT = 10000; + private static String staticWrittenInBaseButReadLater; + @Override public String getModuleName() { return "com.google.gwt.dev.jjs.CompilerSuite"; } - private static final String HELLO = "hello"; - - private static String staticWrittenInBaseButReadLater; - public void testBasic() { delayTestFinish(RUNASYNC_TIMEOUT); @@ -71,4 +72,74 @@ }); } + /** + * Test that callbacks are called in the order they are posted. + */ + public void testOrder() { + class Util { + int callNumber = 0; + int seen = 0; + + void scheduleCallback() { + final int thisCallNumber = callNumber++; + + GWT.runAsync(new RunAsyncCallback() { + + public void onFailure(Throwable caught) { + throw new RuntimeException(caught); + } + + public void onSuccess() { + assertEquals(seen, thisCallNumber); + seen++; + if (seen == 3) { + finishTest(); + } + } + }); + } + } + Util util = new Util(); + + delayTestFinish(RUNASYNC_TIMEOUT); + + util.scheduleCallback(); + util.scheduleCallback(); + util.scheduleCallback(); + } + + public void testUnhandledExceptions() { + // Create an exception that will be thrown from an onSuccess method + final RuntimeException toThrow = new RuntimeException( + "Should be caught by the uncaught exception handler"); + + // save the original handler + final UncaughtExceptionHandler originalHandler = GWT.getUncaughtExceptionHandler(); + + // set a handler that catches toThrow and nothing else + GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { + GWT.setUncaughtExceptionHandler(originalHandler); + + if (e == toThrow) { + // expected + finishTest(); + } else { + // some other exception; pass it on + throw new RuntimeException(e); + } + } + }); + + delayTestFinish(RUNASYNC_TIMEOUT); + + GWT.runAsync(new RunAsyncCallback() { + public void onFailure(Throwable caught) { + } + + public void onSuccess() { + throw toThrow; + } + }); + } }