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

com.google.gwt.dev.codeserver.Job Maven / Gradle / Ivy

/*
 * Copyright 2014 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.codeserver;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.codeserver.JobEvent.CompileStrategy;
import com.google.gwt.dev.codeserver.JobEvent.Status;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedMap;
import com.google.gwt.thirdparty.guava.common.util.concurrent.Futures;
import com.google.gwt.thirdparty.guava.common.util.concurrent.ListenableFuture;
import com.google.gwt.thirdparty.guava.common.util.concurrent.SettableFuture;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A request for Super Dev Mode to compile something.
 *
 * 

Each job has a lifecycle where it goes through up to four states. See * {@link JobEvent.Status}. * *

Jobs are thread-safe. */ class Job { private static final ConcurrentMap prefixToNextId = new ConcurrentHashMap(); // Primary key private final String id; // Input private final String inputModuleName; private final ImmutableSortedMap bindingProperties; // Output private final SettableFuture result = SettableFuture.create(); // Listeners private final Outbox outbox; private final RecompileListener recompileListener; private final JobChangeListener jobChangeListener; private final LogSupplier logSupplier; private JobEventTable table; // non-null when submitted // Miscellaneous private final ImmutableList args; private final Set tags; /** * The id to report to the recompile listener. */ private int compileId = -1; // non-negative after the compile has started private CompileDir compileDir; // non-null after the compile has started private CompileStrategy compileStrategy; // non-null after the compile has started private String outputModuleName; // non-null after successful compile private Exception listenerFailure; /** * Creates a job to update an outbox. * @param bindingProperties Properties that uniquely identify a permutation. * (Otherwise, more than one permutation will be compiled.) * @param parentLogger The parent of the logger that will be used for this job. */ Job(Outbox box, Map bindingProperties, TreeLogger parentLogger, Options options) { this.id = chooseNextId(box); this.outbox = box; this.inputModuleName = box.getInputModuleName(); // TODO: we will use the binding properties to find or create the outbox, // then take binding properties from the outbox here. this.bindingProperties = ImmutableSortedMap.copyOf(bindingProperties); this.recompileListener = Preconditions.checkNotNull(options.getRecompileListener()); this.jobChangeListener = Preconditions.checkNotNull(options.getJobChangeListener()); this.args = Preconditions.checkNotNull(options.getArgs()); this.tags = Preconditions.checkNotNull(options.getTags()); this.logSupplier = new LogSupplier(parentLogger, id); } static boolean isValidJobId(String id) { return ModuleDef.isValidModuleName(id); } private static String chooseNextId(Outbox box) { String prefix = box.getId(); prefixToNextId.putIfAbsent(prefix, new AtomicInteger(0)); return prefix + "_" + prefixToNextId.get(prefix).getAndIncrement(); } /** * A string uniquely identifying this job (within this process). * *

Note that the number doesn't have any particular relationship * with the output directory's name since jobs can be submitted out of order. */ String getId() { return id; } /** * The module name that will be sent to the compiler. */ String getInputModuleName() { return inputModuleName; } /** * The binding properties to use for this recompile. */ ImmutableSortedMap getBindingProperties() { return bindingProperties; } /** * The outbox that will serve the job's result (if successful). */ Outbox getOutbox() { return outbox; } /** * Returns the logger for this job. (Creates it on first use.) */ TreeLogger getLogger() { return logSupplier.get(); } /** * Blocks until we have the result of this recompile. */ Result waitForResult() { return Futures.getUnchecked(getFutureResult()); } /** * Returns a Future that will contain the result of this recompile. */ ListenableFuture getFutureResult() { return result; } Exception getListenerFailure() { return listenerFailure; } // === state transitions === /** * Returns true if this job has been submitted to the JobRunner. * (That is, if {@link #onSubmitted} has ever been called.) */ synchronized boolean wasSubmitted() { return table != null; } boolean isDone() { return result.isDone(); } /** * Reports that this job has been submitted to the JobRunner. * Starts sending updates to the JobTable. * @throws IllegalStateException if the job was already started. */ synchronized void onSubmitted(JobEventTable table) { if (wasSubmitted()) { throw new IllegalStateException("compile job has already started: " + id); } this.table = table; table.publish(makeEvent(Status.WAITING), getLogger()); } /** * Reports that we started to compile the job. */ synchronized void onStarted(int compileId, CompileDir compileDir) { if (table == null || !table.isActive(this)) { throw new IllegalStateException("compile job is not active: " + id); } this.compileId = compileId; this.compileDir = compileDir; try { recompileListener.startedCompile(inputModuleName, compileId, compileDir); } catch (Exception e) { getLogger().log(TreeLogger.Type.WARN, "recompile listener threw exception", e); listenerFailure = e; } publish(makeEvent(Status.COMPILING)); } /** * Reports that this job has made progress while compiling. * @throws IllegalStateException if the job is not running. */ synchronized void onProgress(String stepMessage) { checkIsCompiling("onProgress"); publish(makeEvent(Status.COMPILING, stepMessage)); } synchronized void setCompileStrategy(CompileStrategy strategy) { checkIsCompiling("setCompileStrategy"); if (compileStrategy != null) { throw new IllegalStateException("setCompileStrategy can only be set once per job"); } this.compileStrategy = strategy; // Not bothering to send an event just for this change, so it will be included // in the next event. } /** * Reports that this job has finished. * @throws IllegalStateException if the job is not running. */ synchronized void onFinished(Result newResult) { if (table == null || !table.isActive(this)) { throw new IllegalStateException("compile job is not active: " + id); } // Report that we finished unless the listener messed up already. if (listenerFailure == null) { try { recompileListener.finishedCompile(inputModuleName, compileId, newResult.isOk()); } catch (Exception e) { getLogger().log(TreeLogger.Type.WARN, "recompile listener threw exception", e); listenerFailure = e; } } result.set(newResult); outputModuleName = newResult.outputModuleName; if (newResult.isOk()) { publish(makeEvent(Status.SERVING)); } else { publish(makeEvent(Status.ERROR)); } } /** * Reports that this job's output is no longer available. */ synchronized void onGone() { if (table == null || !table.isActive(this)) { throw new IllegalStateException("compile job is not active: " + id); } publish(makeEvent(Status.GONE)); } private JobEvent makeEvent(Status status) { return makeEvent(status, null); } private JobEvent makeEvent(Status status, String message) { JobEvent.Builder out = new JobEvent.Builder(); out.setJobId(getId()); out.setInputModuleName(getInputModuleName()); out.setBindings(getBindingProperties()); out.setStatus(status); out.setMessage(message); out.setOutputModuleName(outputModuleName); out.setCompileDir(compileDir); out.setCompileStrategy(compileStrategy); out.setArguments(args); out.setTags(tags); out.setMetricMap(getMetricMapSnapshot()); return out.build(); } private Map getMetricMapSnapshot() { TreeLogger logger = getLogger(); if (logger instanceof AbstractTreeLogger) { return ((AbstractTreeLogger)logger).getMetricMap().getSnapshot(); } return ImmutableMap.of(); // not found } /** * Makes an event visible externally. */ private void publish(JobEvent event) { if (listenerFailure == null) { try { jobChangeListener.onJobChange(event); } catch (Exception e) { getLogger().log(Type.WARN, "JobChangeListener threw exception", e); listenerFailure = e; } } table.publish(event, getLogger()); } private void checkIsCompiling(String methodName) { if (table == null || table.getPublishedEvent(this).getStatus() != Status.COMPILING) { throw new IllegalStateException(methodName + " called for a job that isn't compiling: " + id); } } /** * Creates a child logger on first use. */ static class LogSupplier { private final TreeLogger parent; private final String jobId; private TreeLogger child; LogSupplier(TreeLogger parent, String jobId) { this.parent = parent; this.jobId = jobId; } synchronized TreeLogger get() { if (child == null) { child = parent.branch(Type.INFO, "Job " + jobId); if (child instanceof AbstractTreeLogger) { ((AbstractTreeLogger)child).resetMetricMap(); } } return child; } } /** * The result of a recompile. */ static class Result { /** * non-null if successful */ final CompileDir outputDir; /** * non-null if successful. */ final String outputModuleName; /** * non-null for an error */ final Throwable error; Result(CompileDir outputDir, String outputModuleName, Throwable error) { assert (outputDir == null) != (error == null); this.outputDir = outputDir; this.outputModuleName = outputModuleName; this.error = error; } boolean isOk() { return error == null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy