com.google.gwt.dev.PermutationWorkerFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.dev.PermutationWorkerFactory.java

Source

/*
 * 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();
}