blob: 8d2e696c704560a173629d484e65f0a0b2831a22 [file] [log] [blame]
/*
* 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.dev;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.jjs.PermutationResult;
import com.google.gwt.dev.jjs.UnifiedAst;
import com.google.gwt.dev.util.FileBackedObject;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Represents a factory for implementations of an endpoint that will invoke
* CompilePerms. Implementations of PermutationWorkerFactory should be
* default-instantiable and will have {@link #init} called immediately after
* construction.
*/
public abstract class PermutationWorkerFactory {
/**
* Coordinates the actions of a set of {@link PermutationWorker}s, running
* each in its own thread.
*/
private static class Manager {
private static enum Result {
SUCCESS, FAIL, WORKER_DEATH
}
/**
* Runs a {@link PermutationWorker} on its own thread.
*/
private class WorkerThread implements Runnable {
private final PermutationWorker worker;
public WorkerThread(PermutationWorker worker) {
this.worker = worker;
}
public void run() {
Result threadDeathResult = Result.FAIL;
try {
while (true) {
Work work = workQueue.take();
if (work == POISON_PILL) {
return;
}
TreeLogger logger = work.getLogger();
try {
worker.compile(logger, work.getPerm(), work.getResultFile());
logger.log(TreeLogger.DEBUG, "Successfully compiled permutation");
resultsQueue.put(Result.SUCCESS);
} catch (TransientWorkerException e) {
logger.log(TreeLogger.DEBUG,
"Worker died, will retry Permutation", e);
workQueue.add(work);
threadDeathResult = Result.WORKER_DEATH;
return;
} catch (UnableToCompleteException e) {
logger.log(TreeLogger.ERROR,
"Unrecoverable exception, shutting down", e);
return;
}
}
} catch (InterruptedException e) {
return;
} finally {
// Record why I died.
try {
resultsQueue.put(threadDeathResult);
} catch (InterruptedException ignored) {
}
}
}
}
private static final Work POISON_PILL = new Work(null, null, null);
public static void run(TreeLogger logger, List<Work> work,
List<PermutationWorker> workers) throws UnableToCompleteException {
new Manager().doRun(logger, work, workers);
}
/**
* The queue of work to do.
*/
BlockingQueue<Work> workQueue;
/**
* The queue of work to do.
*/
BlockingQueue<Result> resultsQueue;
private Manager() {
}
private void doRun(TreeLogger logger, List<Work> work,
List<PermutationWorker> workers) throws UnableToCompleteException {
// Initialize state.
workQueue = new LinkedBlockingQueue<Work>(work);
resultsQueue = new LinkedBlockingQueue<Result>();
List<Thread> threads = new ArrayList<Thread>(workers.size());
try {
for (PermutationWorker worker : workers) {
Thread thread = new Thread(new WorkerThread(worker), worker.getName());
threads.add(thread);
thread.start();
}
int workToDo = work.size();
int aliveWorkers = workers.size();
waitForWorkers : while (workToDo > 0 && aliveWorkers > 0) {
Event blockedEvent = SpeedTracerLogger.start(CompilerEventType.BLOCKED);
Result take = resultsQueue.take();
blockedEvent.end();
switch (take) {
case SUCCESS:
--workToDo;
break;
case FAIL:
break waitForWorkers;
case WORKER_DEATH:
--aliveWorkers;
break;
default:
throw new IncompatibleClassChangeError(Result.class.toString());
}
}
workQueue.clear();
for (int i = 0; i < aliveWorkers; ++i) {
workQueue.add(POISON_PILL);
}
if (workToDo > 0) {
logger.log(TreeLogger.ERROR,
"Not all permutation were compiled , completed ("
+ (work.size() - workToDo) + "/" + work.size() + ")");
throw new UnableToCompleteException();
}
} catch (InterruptedException e) {
logger.log(TreeLogger.ERROR,
"Exiting without results due to interruption", e);
throw new UnableToCompleteException();
} finally {
// Interrupt any outstanding threads.
for (Thread thread : threads) {
thread.interrupt();
}
}
}
}
/**
* Represents work to do.
*/
private static class Work {
private final TreeLogger logger;
private final Permutation perm;
private final FileBackedObject<PermutationResult> resultFile;
public Work(TreeLogger logger, Permutation perm,
FileBackedObject<PermutationResult> resultFile) {
this.logger = logger;
this.perm = perm;
this.resultFile = resultFile;
}
public TreeLogger getLogger() {
return logger;
}
public Permutation getPerm() {
return perm;
}
public FileBackedObject<PermutationResult> getResultFile() {
return resultFile;
}
}
/**
* The name of the system property used to define the workers.
*/
public static final String FACTORY_IMPL_PROPERTY = "gwt.jjs.permutationWorkerFactory";
/**
* This value can be passed into {@link #setLocalWorkers(int)} to indicate
* that a heuristic should be used to determine the total number of local
* workers.
*/
public static final int WORKERS_AUTO = 0;
/**
* Compiles all Permutations in a Precompilation and returns an array of Files
* that can be consumed by Link using the system-default
* PermutationWorkersFactories.
*/
public static void compilePermutations(TreeLogger logger,
Precompilation precompilation, int localWorkers,
List<FileBackedObject<PermutationResult>> resultFiles)
throws UnableToCompleteException {
compilePermutations(logger, precompilation,
precompilation.getPermutations(), localWorkers, resultFiles);
}
/**
* Compiles a subset of the Permutations in a Precompilation and returns an
* array of Files that can be consumed by Link using the system-default
* PermutationWorkersFactories.
*
* @param localWorkers Set the maximum number of workers that should be
* executed on the local system by the PermutationWorkerFactory. The
* value {@link #WORKERS_AUTO} will allow the
* PermutationWorkerFactory to apply a heuristic to determine the
* correct number of local workers.
* @param resultFiles the output files to write into; must be the same length
* as permutations
*/
public static void compilePermutations(TreeLogger logger,
Precompilation precompilation, Permutation[] permutations,
int localWorkers, List<FileBackedObject<PermutationResult>> resultFiles)
throws UnableToCompleteException {
assert permutations.length == resultFiles.size();
assert Arrays.asList(precompilation.getPermutations()).containsAll(
Arrays.asList(permutations));
// Create the work.
List<Work> work = new ArrayList<Work>(permutations.length);
for (int i = 0; i < permutations.length; ++i) {
Permutation perm = permutations[i];
if (logger.isLoggable(TreeLogger.DEBUG)) {
logger.log(TreeLogger.DEBUG,
"Creating worker permutation " + perm.getId() + " of " + permutations.length);
}
work.add(new Work(logger, perm, resultFiles.get(i)));
}
// Create the workers.
List<PermutationWorker> workers = new ArrayList<PermutationWorker>();
try {
createWorkers(logger, precompilation.getUnifiedAst(), work.size(),
localWorkers, workers);
// Get it done!
Manager.run(logger, work, workers);
} finally {
Throwable caught = null;
for (PermutationWorker worker : workers) {
try {
worker.shutdown();
} catch (Throwable e) {
caught = e;
}
}
if (caught != null) {
throw new RuntimeException(
"One of the workers threw an exception while shutting down", caught);
}
}
}
/**
* Creates one or more implementations of worker factories. This will treat
* the value of the {@value #FACTORY_IMPL_PROPERTY} system property as a
* comma-separated list of type names.
*/
private static synchronized List<PermutationWorkerFactory> createAll(
TreeLogger logger) throws UnableToCompleteException {
// NB: This is the much-derided FactoryFactory pattern
logger = logger.branch(TreeLogger.TRACE,
"Creating PermutationWorkerFactory instances");
List<PermutationWorkerFactory> mutableFactories = new ArrayList<PermutationWorkerFactory>();
String classes = System.getProperty(FACTORY_IMPL_PROPERTY,
ThreadedPermutationWorkerFactory.class.getName() + ","
+ ExternalPermutationWorkerFactory.class.getName());
if (logger.isLoggable(TreeLogger.SPAM)) {
logger.log(TreeLogger.SPAM, "Factory impl property is " + classes);
}
String[] classParts = classes.split(",");
for (String className : classParts) {
try {
Class<? extends PermutationWorkerFactory> clazz = Class.forName(
className).asSubclass(PermutationWorkerFactory.class);
PermutationWorkerFactory factory = clazz.newInstance();
factory.init(logger);
mutableFactories.add(factory);
if (logger.isLoggable(TreeLogger.SPAM)) {
logger.log(TreeLogger.SPAM, "Added PermutationWorkerFactory "
+ clazz.getName());
}
} catch (ClassCastException e) {
logger.log(TreeLogger.ERROR, className + " is not a "
+ PermutationWorkerFactory.class.getName());
} catch (ClassNotFoundException e) {
logger.log(TreeLogger.ERROR,
"Unable to find PermutationWorkerFactory named " + className);
} catch (InstantiationException e) {
logger.log(TreeLogger.ERROR,
"Unable to instantiate PermutationWorkerFactory " + className, e);
} catch (IllegalAccessException e) {
logger.log(TreeLogger.ERROR,
"Unable to instantiate PermutationWorkerFactory " + className, e);
}
}
if (mutableFactories.size() == 0) {
logger.log(TreeLogger.ERROR,
"No usable PermutationWorkerFactories available");
throw new UnableToCompleteException();
}
return Collections.unmodifiableList(mutableFactories);
}
/**
* Create as many workers as possible to service the Permutations.
*/
private static void createWorkers(TreeLogger logger, UnifiedAst unifiedAst,
int workersNeeded, int localWorkers, List<PermutationWorker> workers)
throws UnableToCompleteException {
if (localWorkers <= WORKERS_AUTO) {
// TODO: something smarter?
localWorkers = 1;
}
for (PermutationWorkerFactory factory : PermutationWorkerFactory.createAll(logger)) {
if (workersNeeded <= 0) {
break;
}
int wanted = factory.isLocal() ? Math.min(workersNeeded, localWorkers)
: workersNeeded;
if (wanted <= 0) {
continue;
}
Collection<PermutationWorker> newWorkers = factory.getWorkers(logger,
unifiedAst, wanted);
workers.addAll(newWorkers);
workersNeeded -= newWorkers.size();
if (factory.isLocal()) {
localWorkers -= newWorkers.size();
}
}
if (workers.size() == 0) {
logger.log(TreeLogger.ERROR, "No PermutationWorkers created");
throw new UnableToCompleteException();
}
}
/**
* Return some number of PermutationWorkers.
*
* @param unifiedAst a UnifiedAst
* @param numWorkers the desired number of workers
* @return a collection of PermutationWorkers, the size of which may be less
* than <code>numWorkers</code>
*/
public abstract Collection<PermutationWorker> getWorkers(TreeLogger logger,
UnifiedAst unifiedAst, int numWorkers) throws UnableToCompleteException;
/**
* Initialize the PermutationWorkerFactory.
*/
public abstract void init(TreeLogger logger) throws UnableToCompleteException;
/**
* Indicates if the PermutationWorkers created by the factory consume
* computational or memory resources on the local system, as opposed to the
* per-permutation work being performed on a remote system.
*/
public abstract boolean isLocal();
}