/***********************************************************************************************************************
* Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu)
*
* 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 eu.stratosphere.nephele.taskmanager;
import eu.stratosphere.configuration.Configuration;
import eu.stratosphere.nephele.execution.Environment;
import eu.stratosphere.nephele.execution.ExecutionListener;
import eu.stratosphere.nephele.execution.ExecutionObserver;
import eu.stratosphere.nephele.execution.ExecutionState;
import eu.stratosphere.nephele.execution.ExecutionStateTransition;
import eu.stratosphere.nephele.execution.RuntimeEnvironment;
import eu.stratosphere.nephele.executiongraph.ExecutionVertexID;
import eu.stratosphere.nephele.jobgraph.JobID;
import eu.stratosphere.nephele.profiling.TaskManagerProfiler;
import eu.stratosphere.nephele.services.memorymanager.MemoryManager;
import eu.stratosphere.nephele.template.AbstractInvokable;
import eu.stratosphere.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public final class Task implements ExecutionObserver {
/**
* The log object used for debugging.
*/
private static final Log LOG = LogFactory.getLog(Task.class);
private final ExecutionVertexID vertexID;
private final RuntimeEnvironment environment;
private final TaskManager taskManager;
/**
* Stores whether the task has been canceled.
*/
private volatile boolean isCanceled = false;
/**
* The current execution state of the task
*/
private volatile ExecutionState executionState = ExecutionState.STARTING;
private Queue<ExecutionListener> registeredListeners = new ConcurrentLinkedQueue<ExecutionListener>();
public Task(ExecutionVertexID vertexID, final RuntimeEnvironment environment, TaskManager taskManager) {
this.vertexID = vertexID;
this.environment = environment;
this.taskManager = taskManager;
this.environment.setExecutionObserver(this);
}
/**
* Returns the ID of the job this task belongs to.
*
* @return the ID of the job this task belongs to
*/
public JobID getJobID() {
return this.environment.getJobID();
}
/**
* Returns the ID of this task.
*
* @return the ID of this task
*/
public ExecutionVertexID getVertexID() {
return this.vertexID;
}
/**
* Returns the environment associated with this task.
*
* @return the environment associated with this task
*/
public Environment getEnvironment() {
return this.environment;
}
/**
* Marks the task as failed and triggers the appropriate state changes.
*/
public void markAsFailed() {
executionStateChanged(ExecutionState.FAILED, "Execution thread died unexpectedly");
}
public void cancelExecution() {
cancelOrKillExecution(true);
}
public void killExecution() {
cancelOrKillExecution(false);
}
/**
* Cancels or kills the task.
*
* @param cancel <code>true/code> if the task shall be canceled, <code>false</code> if it shall be killed
*/
private void cancelOrKillExecution(boolean cancel) {
final Thread executingThread = this.environment.getExecutingThread();
if (executingThread == null) {
return;
}
if (this.executionState != ExecutionState.RUNNING && this.executionState != ExecutionState.FINISHING) {
return;
}
LOG.info((cancel ? "Canceling " : "Killing ") + this.environment.getTaskNameWithIndex());
if (cancel) {
this.isCanceled = true;
// Change state
executionStateChanged(ExecutionState.CANCELING, null);
// Request user code to shut down
try {
final AbstractInvokable invokable = this.environment.getInvokable();
if (invokable != null) {
invokable.cancel();
}
} catch (Throwable e) {
LOG.error(StringUtils.stringifyException(e));
}
}
// Continuously interrupt the user thread until it changed to state CANCELED
while (true) {
executingThread.interrupt();
if (!executingThread.isAlive()) {
break;
}
try {
executingThread.join(1000);
} catch (InterruptedException e) {}
if (!executingThread.isAlive()) {
break;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Sending repeated " + (cancel == true ? "canceling" : "killing") + " signal to " +
this.environment.getTaskName() + " with state " + this.executionState);
}
}
}
/**
* Checks if the state of the thread which is associated with this task is <code>TERMINATED</code>.
*
* @return <code>true</code> if the state of this thread which is associated with this task is
* <code>TERMINATED</code>, <code>false</code> otherwise
*/
public boolean isTerminated() {
final Thread executingThread = this.environment.getExecutingThread();
if (executingThread.getState() == Thread.State.TERMINATED) {
return true;
}
return false;
}
/**
* Starts the execution of this task.
*/
public void startExecution() {
final Thread thread = this.environment.getExecutingThread();
thread.start();
}
/**
* Registers the task manager profiler with the task.
*
* @param taskManagerProfiler
* the task manager profiler
* @param jobConfiguration
* the configuration attached to the job
*/
public void registerProfiler(final TaskManagerProfiler taskManagerProfiler, final Configuration jobConfiguration) {
taskManagerProfiler.registerExecutionListener(this, jobConfiguration);
}
/**
* Unregisters the task from the central memory manager.
*
* @param memoryManager
* the central memory manager
*/
public void unregisterMemoryManager(final MemoryManager memoryManager) {
if (memoryManager != null) {
memoryManager.releaseAll(this.environment.getInvokable());
}
}
/**
* Unregisters the task from the task manager profiler.
*
* @param taskManagerProfiler
* the task manager profiler
*/
public void unregisterProfiler(final TaskManagerProfiler taskManagerProfiler) {
if (taskManagerProfiler != null) {
taskManagerProfiler.unregisterExecutionListener(this.vertexID);
}
}
/**
* Returns the current execution state of the task.
*
* @return the current execution state of the task
*/
public ExecutionState getExecutionState() {
return this.executionState;
}
// -----------------------------------------------------------------------------------------------------------------
// ExecutionObserver methods
// -----------------------------------------------------------------------------------------------------------------
@Override
public void executionStateChanged(final ExecutionState newExecutionState, final String optionalMessage) {
// Check the state transition
ExecutionStateTransition.checkTransition(false, getTaskName(), this.executionState, newExecutionState);
// Make sure the reason for a transition to FAILED appears in the log files
if (newExecutionState == ExecutionState.FAILED) {
LOG.error(optionalMessage);
}
// Notify all listener objects
final Iterator<ExecutionListener> it = this.registeredListeners.iterator();
while (it.hasNext()) {
it.next().executionStateChanged(this.environment.getJobID(), this.vertexID, newExecutionState,
optionalMessage);
}
// Store the new execution state
this.executionState = newExecutionState;
// Finally propagate the state change to the job manager
this.taskManager.executionStateChanged(this.environment.getJobID(), this.vertexID, newExecutionState,
optionalMessage);
}
/**
* Returns the name of the task associated with this observer object.
*
* @return the name of the task associated with this observer object
*/
private String getTaskName() {
return this.environment.getTaskName() + " (" + (this.environment.getIndexInSubtaskGroup() + 1) + "/"
+ this.environment.getCurrentNumberOfSubtasks() + ")";
}
@Override
public void userThreadStarted(final Thread userThread) {
// Notify the listeners
final Iterator<ExecutionListener> it = this.registeredListeners.iterator();
while (it.hasNext()) {
it.next().userThreadStarted(this.environment.getJobID(), this.vertexID, userThread);
}
}
@Override
public void userThreadFinished(final Thread userThread) {
// Notify the listeners
final Iterator<ExecutionListener> it = this.registeredListeners.iterator();
while (it.hasNext()) {
it.next().userThreadFinished(this.environment.getJobID(), this.vertexID, userThread);
}
}
/**
* Registers the {@link ExecutionListener} object for this task. This object
* will be notified about important events during the task execution.
*
* @param executionListener
* the object to be notified for important events during the task execution
*/
public void registerExecutionListener(final ExecutionListener executionListener) {
this.registeredListeners.add(executionListener);
}
/**
* Unregisters the {@link ExecutionListener} object for this environment. This object
* will no longer be notified about important events during the task execution.
*
* @param executionListener
* the lister object to be unregistered
*/
public void unregisterExecutionListener(final ExecutionListener executionListener) {
this.registeredListeners.remove(executionListener);
}
@Override
public boolean isCanceled() {
return this.isCanceled;
}
/**
* Returns the runtime environment associated with this task.
*
* @return the runtime environment associated with this task
*/
public RuntimeEnvironment getRuntimeEnvironment() {
return this.environment;
}
}