All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.junit.RunStyleRemoteWeb Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.junit;

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.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.rmi.Naming;
import java.rmi.server.RMISocketFactory;
import java.util.HashSet;
import java.util.Set;

/**
 * Runs in Production Mode via browsers managed over RMI. This feature is
 * experimental and is not officially supported.
 */
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 class RemoteBrowser {
    /**
     * Remote browser manager.
     */
    private final BrowserManager manager;

    /**
     * RMI URLs for the remote browser server.
     */
    private final String rmiUrl;
    /**
     * Reference to the remote browser processes.
     */
    private int token;

    public RemoteBrowser(BrowserManager manager, String rmiUrl) {
      this.manager = manager;
      this.rmiUrl = rmiUrl;
    }

    public BrowserManager getManager() {
      return manager;
    }

    public String getRmiUrl() {
      return rmiUrl;
    }

    public int getToken() {
      return token;
    }

    public void setToken(int token) {
      this.token = token;
    }
  }

  /**
   * Registered as a shutdown hook to make sure that any browsers that were not
   * finished are killed.
   */
  private class ShutdownCb extends Thread {

    @Override
    public synchronized void run() {
      for (RemoteBrowser remoteBrowser : remoteBrowsers) {
        int remoteToken = remoteBrowser.getToken();
        if (remoteToken > 0) {
          try {
            remoteBrowser.getManager().killBrowser(remoteToken);
          } catch (Exception e) {
            System.err.println("Error killing remote browser during shutdown: "
                + remoteBrowser.getRmiUrl());
            e.printStackTrace();
          }
          // We've done our best to kill it. Don't try anymore.
          remoteBrowser.setToken(0);
        }
      }
    }
  }

  private static final int CONNECT_MS = 10000;
  private static final int PING_KEEPALIVE_MS = 5000;

  // 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 PING_KEEPALIVE_MS = 500000;
  // private static final int RESPONSE_TIMEOUT_MS = 1000000;

  private static final int RESPONSE_TIMEOUT_MS = 10000;

  /**
   * The list of hosts that were interrupted.
   */
  private Set interruptedHosts;

  private RemoteBrowser[] remoteBrowsers;

  /**
   * A separate lock to control access to {@link #interruptedHosts}. This keeps
   * the main thread calls into {@link #getInterruptedHosts()} from having to
   * synchronized on the containing instance and potentially block on RPC calls.
   * It is okay to take the {@link #wasInterruptedLock} while locking the
   * containing instance; it is NOT okay to do the opposite or deadlock could
   * occur.
   */
  private final Object wasInterruptedLock = new Object();

  /**
   * @param shell the containing shell
   */
  public RunStyleRemoteWeb(JUnitShell shell) {
    super(shell);
  }

  @Override
  public String[] getInterruptedHosts() {
    synchronized (wasInterruptedLock) {
      if (interruptedHosts == null) {
        return null;
      }
      return interruptedHosts.toArray(new String[interruptedHosts.size()]);
    }
  }

  @Override
  public int initialize(String args) {
    if (args == null || args.length() == 0) {
      getLogger().log(TreeLogger.ERROR,
          "RemoteWeb runstyle requires comma-separated RMI URLs");
      return -1;
    }
    String[] urls = args.split(",");
    try {
      RMISocketFactoryWithTimeouts.init();
    } catch (IOException e) {
      getLogger().log(TreeLogger.ERROR,
          "RemoteWeb: Error initializing RMISocketFactory", e);
      return -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) {
        String message = "RemoteWeb: Error connecting to browser manager at "
            + urls[i];
        Throwable cause = e;
        if (e.getCause() instanceof SocketTimeoutException) {
          long elapsed = System.currentTimeMillis() - callStart;
          message += " - Timeout " + elapsed
              + "ms waiting to connect to browser manager.";
          cause = e.getCause();
        }
        getLogger().log(TreeLogger.ERROR, message, cause);
        return -1;
      }
    }
    synchronized (this) {
      this.remoteBrowsers = new RemoteBrowser[browserManagers.length];
      for (int i = 0; i < browserManagers.length; ++i) {
        remoteBrowsers[i] = new RemoteBrowser(browserManagers[i], urls[i]);
      }
    }
    Runtime.getRuntime().addShutdownHook(new ShutdownCb());
    return numClients;
  }

  @Override
  public synchronized void launchModule(String moduleName)
      throws UnableToCompleteException {
    String url = shell.getModuleUrl(moduleName);

    for (RemoteBrowser remoteBrowser : remoteBrowsers) {
      long callStart = System.currentTimeMillis();
      try {
        int remoteToken = remoteBrowser.getToken();
        BrowserManager mgr = remoteBrowser.getManager();
        if (remoteToken != 0) {
          mgr.killBrowser(remoteToken);
        }
        remoteToken = mgr.launchNewBrowser(url, PING_KEEPALIVE_MS);
        remoteBrowser.setToken(remoteToken);
      } catch (Exception e) {
        Throwable cause = e.getCause();
        if (cause instanceof SocketTimeoutException) {
          long elapsed = System.currentTimeMillis() - callStart;
          getLogger().log(
              TreeLogger.ERROR,
              "Timeout: " + elapsed + "ms  launching remote browser at: "
                  + remoteBrowser.getRmiUrl(), e.getCause());
          throw new UnableToCompleteException();
        }
        getLogger().log(TreeLogger.ERROR,
            "Error launching remote browser at " + remoteBrowser.getRmiUrl(), e);
        throw new UnableToCompleteException();
      }
    }

    Thread keepAliveThread = new Thread() {
      @Override
      public void run() {
        do {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException ignored) {
          }
        } while (doKeepAlives());
      }
    };
    keepAliveThread.setDaemon(true);
    keepAliveThread.start();
  }

  private synchronized boolean doKeepAlives() {
    for (RemoteBrowser remoteBrowser : remoteBrowsers) {
      if (remoteBrowser.getToken() > 0) {
        long callStart = System.currentTimeMillis();
        try {
          remoteBrowser.getManager().keepAlive(remoteBrowser.getToken(),
              PING_KEEPALIVE_MS);
        } catch (Exception e) {
          Throwable cause = e.getCause();
          String rmiUrl = remoteBrowser.getRmiUrl();
          if (cause instanceof SocketTimeoutException) {
            long elapsed = System.currentTimeMillis() - callStart;
            throw new TimeoutException("Timeout: " + elapsed
                + "ms  keeping alive remote browser at: " + rmiUrl,
                e.getCause());
          } else if (e instanceof IllegalStateException) {
            if (getLogger().isLoggable(TreeLogger.INFO)) {
              getLogger().log(TreeLogger.INFO,
                  "Browser at: " + rmiUrl + " already exited.", e);
            }
          } else {
            getLogger().log(TreeLogger.ERROR,
                            "Error keeping alive remote browser at " + rmiUrl, e);
          }
          remoteBrowser.setToken(0);
          synchronized (wasInterruptedLock) {
            if (interruptedHosts == null) {
              interruptedHosts = new HashSet();
            }
            interruptedHosts.add(remoteBrowser.getRmiUrl());
          }
          break;
        }
      }
    }

    synchronized (wasInterruptedLock) {
      return interruptedHosts == null;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy