/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.core.schedulers; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Multimaps.synchronizedMultimap; import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger; import static java.util.Collections.synchronizedMap; import java.util.HashMap; import java.util.Map; import java.util.Set; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import fr.ens.biologie.genomique.eoulsan.core.Step; import fr.ens.biologie.genomique.eoulsan.core.workflow.AbstractStep; import fr.ens.biologie.genomique.eoulsan.core.workflow.TaskContextImpl; import fr.ens.biologie.genomique.eoulsan.core.workflow.TaskResultImpl; import fr.ens.biologie.genomique.eoulsan.core.workflow.TaskRunner; import fr.ens.biologie.genomique.eoulsan.core.workflow.StepResult; import fr.ens.biologie.genomique.eoulsan.core.workflow.StepStatus; /** * This class define an abstract task scheduler. * @author Laurent Jourdren * @since 2.0 */ public abstract class AbstractTaskScheduler implements TaskScheduler { private static final int SLEEP_TIME_IN_MS = 500; private final Multimap<Step, Integer> submittedContexts; private final Multimap<Step, Integer> runningContexts; private final Multimap<Step, Integer> doneContexts; private final Map<Integer, Step> contexts; private final Map<Step, StepStatus> status; private final Map<Step, StepResult> results; private volatile boolean isStarted; private volatile boolean isStopped; private volatile boolean isPaused; // // Protected methods // /** * Add a task result to its step result. * @param step the step of the result * @param result the result to add */ private void addResult(final Step step, final TaskResultImpl result) { this.results.get(step).addResult(result); } /** * Get the step related to a context. * @param context the context * @return the step related to the context */ protected Step getStep(final TaskContextImpl context) { checkNotNull(context, "context argument cannot be null"); return getStep(context.getId()); } /** * Get the step related to a context. * @param contextId the context id * @return the step related to the context */ protected Step getStep(final int contextId) { // Test if the contextId has been submitted checkState(this.contexts.containsKey(contextId), "The context (" + contextId + ") has never been submitted"); return this.contexts.get(contextId); } /** * Set a context in the running state * @param context the context */ private void addRunningContext(final TaskContextImpl context) { checkNotNull(context, "context argument cannot be null"); addRunningContext(context.getId()); } /** * Set a context in the running state * @param contextId the context id */ private void addRunningContext(final int contextId) { // Check execution state checkExecutionState(); // Test if the contextId has been submitted checkState(this.contexts.containsKey(contextId), "The context (" + contextId + ") has never been submitted"); // Test if the context is already running checkState(!this.runningContexts.containsValue(contextId), "The context (" + contextId + ") already running"); // Test if the context has been already done checkState(!this.doneContexts.containsValue(contextId), "The context (" + contextId + ") has been already done"); final Step step = getStep(contextId); synchronized (this) { this.runningContexts.put(step, contextId); } // Update the UI this.status.get(step).setTaskRunning(contextId); getLogger().fine("Scheduler: task #" + contextId + " (step #" + step.getNumber() + " " + step.getId() + ") is running"); } /** * Set a context in done state. * @param context the context */ private void addDoneContext(final TaskContextImpl context) { checkNotNull(context, "context argument cannot be null"); addDoneContext(context.getId()); } /** * Set a context in done state. * @param contextId the context id */ private void addDoneContext(final int contextId) { // Check execution state checkExecutionState(); // Test if the contextId has been submitted checkState(this.contexts.containsKey(contextId), "The context (" + contextId + ") has never been submitted"); // Test if the context is running checkState(this.runningContexts.containsValue(contextId), "The context (" + contextId + ") is not running"); // Test if the context has been already done checkState(!this.doneContexts.containsValue(contextId), "The context (" + contextId + ") has been already done"); final Step step = getStep(contextId); synchronized (this) { this.runningContexts.remove(step, contextId); this.doneContexts.put(step, contextId); } // Update the UI this.status.get(step).setTaskDone(contextId); getLogger().fine("Scheduler: task #" + contextId + " (step #" + step.getNumber() + " " + step.getId() + ") is done"); } /** * Set the state of the context before executing a task. * @param context the context to execute */ protected void beforeExecuteTask(final TaskContextImpl context) { checkNotNull(context, "context argument is null"); // Check execution state checkExecutionState(); // Update counters addRunningContext(context); } /** * Set the state of the context after executing a task. * @param context the context to execute */ protected void afterExecuteTask(final TaskContextImpl context, final TaskResultImpl result) { checkNotNull(context, "context argument is null"); checkNotNull(result, "result argument is null"); // Add the context result to the step result addResult(getStep(context.getId()), result); // Update counters addDoneContext(context); } /** * Default executing context method. * @param context the context * @return a TaskResult object */ protected TaskResultImpl executeTask(final TaskContextImpl context) { checkNotNull(context, "context argument is null"); // Get the step of the context final Step step = getStep(context.getId()); // Create context runner final TaskRunner contextRunner = new TaskRunner(context, getStatus(step)); // Run the step context contextRunner.run(); // Return the result return contextRunner.getResult(); } // // TaskScheduler interface // @Override public void submit(final Step step, final Set<TaskContextImpl> contexts) { checkNotNull(contexts, "contexts argument cannot be null"); for (TaskContextImpl context : contexts) { submit(step, context); } } @Override public void submit(final Step step, final TaskContextImpl context) { // Check execution state checkExecutionState(); checkNotNull(step, "step argument cannot be null"); checkNotNull(context, "context argument cannot be null"); // Test if the context has been already submitted checkState(!this.submittedContexts.containsEntry(step, context.getId()), "The context (#" + context.getId() + ") has been already submitted"); synchronized (this) { // If this the first context of the step if (!this.status.containsKey(step)) { this.status.put(step, new StepStatus((AbstractStep) step)); this.results.put(step, new StepResult((AbstractStep) step)); } this.submittedContexts.put(step, context.getId()); this.contexts.put(context.getId(), step); } // Update the UI this.status.get(step).setTaskSubmitted(context.getId()); getLogger().fine("Scheduler: task #" + context.getId() + " (step #" + step.getNumber() + " " + step.getId() + ") has been submitted"); } @Override public StepStatus getStatus(final Step step) { return this.status.get(step); } @Override public StepResult getResult(final Step step) { return this.results.get(step); } @Override public int getTaskSubmittedCount(final Step step) { checkNotNull(step, "step argument cannot be null"); // Test if contexts for the step has been submitted if (!this.submittedContexts.containsKey(step)) { return 0; } return this.submittedContexts.get(step).size(); } @Override public int getTaskRunningCount(final Step step) { checkNotNull(step, "step argument cannot be null"); // Test if contexts for the step has been submitted if (!this.runningContexts.containsKey(step)) { return 0; } return this.runningContexts.get(step).size(); } @Override public int getTaskDoneCount(final Step step) { checkNotNull(step, "step argument cannot be null"); // Test if contexts for the step has been submitted if (!this.doneContexts.containsKey(step)) { return 0; } return this.doneContexts.get(step).size(); } @Override public int getTotalTaskSubmittedCount() { return this.submittedContexts.size(); } @Override public int getTotalTaskRunningCount() { return this.runningContexts.size(); } @Override public int getTotalTaskDoneCount() { return this.doneContexts.size(); } int getTotalWaitingCount() { return getTotalTaskSubmittedCount() - getTotalTaskRunningCount() - getTotalTaskDoneCount(); } @Override public void waitEndOfTasks(final Step step) { // Check execution state checkExecutionState(); while (!isStopped() && (getTaskRunningCount(step) > 0 || getTaskSubmittedCount(step) > getTaskDoneCount(step))) { try { Thread.sleep(SLEEP_TIME_IN_MS); } catch (InterruptedException e) { getLogger().severe(e.getMessage()); } } } @Override public void start() { // Check execution state checkState(!this.isStopped, "The scheduler is stopped"); synchronized (this) { this.isStarted = true; } } protected boolean isStarted() { return this.isStarted; } @Override public void stop() { // Check execution state checkExecutionState(); synchronized (this) { this.isStopped = true; } } protected boolean isStopped() { return this.isStopped; } /** * Pause the scheduler. */ void pause() { // Check execution state checkExecutionState(); checkState(!this.isPaused, "The execution is already paused"); synchronized (this) { this.isPaused = true; } } /** * Resume the scheduler. */ void resume() { // Check execution state checkExecutionState(); checkState(this.isPaused, "The execution is not paused"); synchronized (this) { this.isPaused = false; } } /** * Test if the scheduler is paused. * @return true if the scheduler is paused */ boolean isPaused() { return this.isPaused; } private void checkExecutionState() { checkState(this.isStarted, "The scheduler is not started"); checkState(!this.isStopped, "The scheduler is stopped"); } // // Constructor // /** * Protected constructor. */ protected AbstractTaskScheduler() { final Multimap<Step, Integer> mm1 = HashMultimap.create(); final Multimap<Step, Integer> mm2 = HashMultimap.create(); final Multimap<Step, Integer> mm3 = HashMultimap.create(); this.submittedContexts = synchronizedMultimap(mm1); this.runningContexts = synchronizedMultimap(mm2); this.doneContexts = synchronizedMultimap(mm3); final Map<Integer, Step> m1 = new HashMap<>(); final Map<Step, StepStatus> m2 = new HashMap<>(); final Map<Step, StepResult> m3 = new HashMap<>(); this.contexts = synchronizedMap(m1); this.status = synchronizedMap(m2); this.results = synchronizedMap(m3); } }