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;
+ }
+ });
+ }
}