One of the BrowserManagerServer tasks in our continous build was not
responding to launchRemoteBrowser() requests. As a result of this problem,
the continuous build is getting stuck building one of the projects. It
will not finish until the junit process is killed manually. The problem
was that the BrowserManager launchRemoteBrowser() task is blocking indefinitely.
This patch addresses the issue by setting a timeout for the read() operation
on the socket used for RMI. The patch also adds specific messages for
exceptions caused by timeouts.
Patch by: zundel
Review by: scottb
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2058 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 95b06e0..33fef48 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -29,7 +29,6 @@
import com.google.gwt.junit.client.TimeoutException;
import com.google.gwt.junit.client.impl.GWTRunner;
import com.google.gwt.junit.client.impl.JUnitResult;
-import com.google.gwt.junit.remote.BrowserManager;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
@@ -37,7 +36,6 @@
import junit.framework.TestCase;
import junit.framework.TestResult;
-import java.rmi.Naming;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@@ -139,7 +137,7 @@
/**
* Entry point for {@link com.google.gwt.junit.client.GWTTestCase}. Gets or
* creates the singleton {@link JUnitShell} and invokes its {@link
- * #runTestImpl(String, TestCase, TestResult)}.
+ * #runTestImpl(String, TestCase, TestResult, Strategy)}.
*/
public static void runTest(String moduleName, TestCase testCase,
TestResult testResult) throws UnableToCompleteException {
@@ -267,21 +265,10 @@
@Override
public boolean setString(String str) {
- try {
- String[] urls = str.split(",");
- numClients = urls.length;
- BrowserManager[] browserManagers = new BrowserManager[numClients];
- for (int i = 0; i < numClients; ++i) {
- browserManagers[i] = (BrowserManager) Naming.lookup(urls[i]);
- }
- runStyle = new RunStyleRemoteWeb(JUnitShell.this, browserManagers);
- } catch (Exception e) {
- System.err.println("Error connecting to browser manager at " + str);
- e.printStackTrace();
- System.exit(1);
- return false;
- }
- return true;
+ String[] urls = str.split(",");
+ numClients = urls.length;
+ runStyle = RunStyleRemoteWeb.create(JUnitShell.this, urls);
+ return runStyle != null;
}
});
diff --git a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
index ca5835d..c5c5349 100644
--- a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
+++ b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
@@ -17,10 +17,18 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.junit.client.TimeoutException;
import com.google.gwt.junit.remote.BrowserManager;
+import java.io.IOException;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
+import java.rmi.Naming;
+import java.rmi.server.RMISocketFactory;
/**
* Runs remotely in web mode. This feature is experimental and is not officially
@@ -28,13 +36,73 @@
*/
class RunStyleRemoteWeb extends RunStyle {
+ static class RMISocketFactoryWithTimeouts extends RMISocketFactory {
+ private static boolean initialized;
+
+ public static void init() throws IOException {
+ if (!initialized) {
+ RMISocketFactory.setSocketFactory(new RMISocketFactoryWithTimeouts());
+ initialized = true;
+ }
+ }
+
+ @Override
+ public ServerSocket createServerSocket(int port) throws IOException {
+ return RMISocketFactory.getDefaultSocketFactory().createServerSocket(port);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ Socket socket = new Socket();
+ socket.connect(new InetSocketAddress(host, port), CONNECT_MS);
+ socket.setSoTimeout(RESPONSE_TIMEOUT_MS);
+ return socket;
+ }
+ }
+
+ private static final int CONNECT_MS = 10000;
private static final int INITIAL_KEEPALIVE_MS = 5000;
private static final int PING_KEEPALIVE_MS = 2000;
+ private static final int RESPONSE_TIMEOUT_MS = 10000;
// Larger values when debugging the unit test framework, so you
// don't get spurious timeouts.
+ // private static final int CONNECT_MS = 1000000;
// private static final int INITIAL_KEEPALIVE_MS = 500000;
// private static final int PING_KEEPALIVE_MS = 200000;
+ // private static final int RESPONSE_TIMEOUT_MS = 1000000;
+
+ public static RunStyle create(JUnitShell shell, String[] urls) {
+ try {
+ RMISocketFactoryWithTimeouts.init();
+ } catch (IOException e) {
+ System.err.println("Error initializing RMISocketFactory");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ int numClients = urls.length;
+ BrowserManager[] browserManagers = new BrowserManager[numClients];
+ for (int i = 0; i < numClients; ++i) {
+ long callStart = System.currentTimeMillis();
+ try {
+ browserManagers[i] = (BrowserManager) Naming.lookup(urls[i]);
+ } catch (Exception e) {
+ if (e.getCause() instanceof SocketTimeoutException) {
+ long elapsed = System.currentTimeMillis() - callStart;
+ System.err.println("Timeout " + elapsed
+ + "ms waiting for browser manager server to connect at "
+ + urls[i]);
+ e.getCause().printStackTrace();
+ } else {
+ System.err.println("Error connecting to browser manager at "
+ + urls[i]);
+ e.printStackTrace();
+ }
+ System.exit(1);
+ }
+ }
+ return new RunStyleRemoteWeb(shell, browserManagers, urls);
+ }
/**
* Remote browser managers.
@@ -42,24 +110,36 @@
private final BrowserManager[] browserManagers;
/**
+ * RMI URLs for each remote browser server (using the same index as
+ * browserManagers[] and remoteTokens[]).
+ */
+ private String remoteBmsUrls[];
+
+ /**
* References to the remote browser processes.
*/
private int[] remoteTokens;
+ private boolean running = false;
+
/**
* The containing shell.
*/
private final JUnitShell shell;
- private boolean running = false;
-
/**
* @param shell the containing shell
+ * @param browserManagers a populated array of RMI remote interfaces to each
+ * remote BrowserManagerServer
+ * @param urls the URLs for each BrowserManager - used for error reporting
+ * only
*/
- public RunStyleRemoteWeb(JUnitShell shell, BrowserManager[] browserManagers) {
+ private RunStyleRemoteWeb(JUnitShell shell, BrowserManager[] browserManagers,
+ String[] urls) {
this.shell = shell;
this.browserManagers = browserManagers;
this.remoteTokens = new int[browserManagers.length];
+ this.remoteBmsUrls = urls;
}
@Override
@@ -78,21 +158,31 @@
String url = "http://" + localhost + ":" + shell.getPort() + "/"
+ getUrlSuffix(moduleName);
- try {
- for (int i = 0; i < remoteTokens.length; ++i) {
+ for (int i = 0; i < remoteTokens.length; ++i) {
+ long callStart = System.currentTimeMillis();
+ try {
int remoteToken = remoteTokens[i];
BrowserManager mgr = browserManagers[i];
if (remoteToken != 0) {
mgr.killBrowser(remoteToken);
}
remoteTokens[i] = mgr.launchNewBrowser(url, INITIAL_KEEPALIVE_MS);
+ } catch (Exception e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof SocketTimeoutException) {
+ long elapsed = System.currentTimeMillis()
+ - callStart;
+ shell.getTopLogger().log(
+ TreeLogger.ERROR,
+ "Timeout: " + elapsed + "ms launching remote browser at: "
+ + remoteBmsUrls[i], e.getCause());
+ throw new UnableToCompleteException();
+ }
+ shell.getTopLogger().log(TreeLogger.ERROR,
+ "Error launching remote browser at " + remoteBmsUrls[i], e);
+ throw new UnableToCompleteException();
}
- } catch (Exception e) {
- shell.getTopLogger().log(TreeLogger.ERROR,
- "Error launching remote browser", e);
- throw new UnableToCompleteException();
}
-
running = true;
}
}
@@ -103,18 +193,29 @@
int remoteToken = remoteTokens[i];
BrowserManager mgr = browserManagers[i];
if (remoteToken > 0) {
+
+ long callStart = System.currentTimeMillis();
try {
mgr.keepAlive(remoteToken, PING_KEEPALIVE_MS);
} catch (Exception e) {
- // TODO(tobyr): We're failing on the first exception, rather than
- // collecting them, but that's probably OK for now.
- shell.getTopLogger().log(TreeLogger.WARN,
- "Unexpected exception keeping remote browser alive", e);
+ Throwable cause = e.getCause();
+ if (cause instanceof SocketTimeoutException) {
+ long elapsed = System.currentTimeMillis()
+ - callStart;
+ throw new TimeoutException("Timeout: " + elapsed
+ + "ms keeping alive remote browser at: " + remoteBmsUrls[i],
+ e.getCause());
+ } else if (e instanceof IllegalStateException) {
+ shell.getTopLogger().log(TreeLogger.INFO,
+ "Browser at: " + remoteBmsUrls[i] + " already exited.", e);
+ } else {
+ shell.getTopLogger().log(TreeLogger.ERROR,
+ "Error keeping alive remote browser at " + remoteBmsUrls[i], e);
+ }
return true;
}
}
}
return false;
}
-
}