package cz.cuni.mff.d3s.been.hostruntime.task; import static cz.cuni.mff.d3s.been.core.task.TaskState.*; import java.util.Collection; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.hazelcast.core.IMap; import cz.cuni.mff.d3s.been.cluster.context.ClusterContext; import cz.cuni.mff.d3s.been.cluster.context.Tasks; import cz.cuni.mff.d3s.been.core.persistence.Entities; import cz.cuni.mff.d3s.been.core.task.*; import cz.cuni.mff.d3s.been.debugassistant.DebugAssistant; import cz.cuni.mff.d3s.been.persistence.DAOException; import cz.cuni.mff.d3s.been.persistence.task.PersistentTaskState; /** * Utility class which encapsulate manipulation of {@link TaskEntry} of a given * task. * * @author Martin Sixta */ public class TaskHandle { /** logging */ private static final Logger log = LoggerFactory.getLogger(TaskHandle.class); /** the entry to take care of */ private final TaskEntry entry; /** connection to the cluster */ private final ClusterContext ctx; /** The map with task entries. */ private final IMap<String, TaskEntry> map; /** ID of the task */ private final String id; /** Tasks utility functions */ private final Tasks tasks; /** * Creates new handle. * * @param entry * entry to manipulate upon * @param ctx * connection to the cluster */ public TaskHandle(TaskEntry entry, ClusterContext ctx) { this.entry = entry; this.ctx = ctx; this.id = entry.getId(); this.map = ctx.getTasks().getTasksMap(); this.tasks = ctx.getTasks(); } /** * * Sets state of the entry to ACCEPTED. * * The change is cluster visible. * * @throws IllegalStateException * when cannot update entry */ public void setAccepted() throws IllegalStateException { updateEntry(ACCEPTED, "Task has been accepted on %s", entry.getRuntimeId()); } /** * * Sets state of the entry to RUNNING. * * The change is cluster visible. * * @param process * where to get runtime information to be updated in the entry * * @throws IllegalStateException * when cannot update entry * */ public void setRunning(TaskProcess process) throws IllegalStateException { entry.setWorkingDirectory(process.getWorkingDirectory()); setTaskEntryArgs(process.getArgs()); updateEntry(TaskState.RUNNING, "Task is going to be run on %s", entry.getRuntimeId()); } /** * Sets state of the entry to FINISHED. * * The change is cluster visible. * * @param exitValue * the exit value of the task * * @throws IllegalStateException * when cannot update entry */ public void setFinished(int exitValue) throws IllegalStateException { entry.setExitCode(exitValue); updateEntry(TaskState.FINISHED, "Task has finished with exit value %d", exitValue); } /** * Sets state of the entry to ABORTED * * @param message * formatted message of why the change happened * * @throws IllegalStateException * when cannot update entry */ public void setAborted(String message) throws IllegalStateException { updateEntry(TaskState.ABORTED, "%s", message); } /** * Sets state of the entry to ABORTED * * @param message * formatted message of why the change happened * @param exitValue * exit code of the aborted process * * @throws IllegalStateException * when cannot update entry * */ public void setAborted(String message, int exitValue) throws IllegalStateException { entry.setExitCode(exitValue); updateEntry(TaskState.ABORTED, "%s", message); } /** * Sets the state of the entry to SUBMITTED. This causes the task to be * rescheduled. * * No further manipulation of the entry is allowed after calling the function * in this context of execution. * * @param format * formatted message of why the change happened * @param args * arguments for the formatted message * * @throws IllegalStateException * when cannot update entry */ public void reSubmit(String format, Object... args) throws IllegalStateException { updateEntry(TaskState.SUBMITTED, format, args); } /** * Returns TaskDescriptor associated with the entry. * * @return TaskDescriptor associated with the entry */ public TaskDescriptor getTaskDescriptor() { return entry.getTaskDescriptor(); } /** * Returns context ID of the task. * * @return context ID of the task */ public String getContextId() { return entry.getTaskContextId(); } /** * Returns ID of the task. * * @return ID of the task */ public String getTaskId() { return entry.getId(); } /** * Returns current exclusivity * * @return current exclusivity */ public TaskExclusivity getExclusivity() { return getTaskDescriptor().getExclusive(); } /** * Updates the entry in the cluster. * * @param state * new state of the task * @param format * formatted message of why the change happened * @param args * arguments for the formatted message * * @throws IllegalStateException * if the current entry has been concurrently modified */ private void updateEntry(TaskState state, String format, Object... args) throws IllegalStateException { map.lock(id); try { TaskEntry clusterEntry = map.get(id); if (clusterEntry == null) { String msg = String.format("No such task entry: %s", id); throw new IllegalStateException(msg); } if (!isSame(clusterEntry)) { String msg = String.format("Task entry '%s' concurrently modified.", id); throw new IllegalStateException(msg); } // change state of the entry TaskEntries.setState(entry, state, format, args); tasks.putTask(entry); } finally { map.unlock(id); } if (FINISHED.equals(state) || ABORTED.equals(state) || RUNNING.equals(state)) { try { PersistentTaskState entity = new PersistentTaskState(); entity.setTaskState(state); entity.setTaskId(id); entity.setContextId(entry.getTaskContextId()); entity.setBenchmarkId(entry.getBenchmarkId()); entity.setRuntimeId(entry.getRuntimeId()); List<StateChangeEntry> logEntries = entry.getStateChangeLog().getLogEntries(); if (logEntries.size() > 0) { entity.setTimeStarted(logEntries.get(0).getTimestamp()); entity.setTimeFinished(logEntries.get(logEntries.size() - 1).getTimestamp()); } ctx.getPersistence().asyncPersist(Entities.OUTCOME_TASK.getId(), entity); } catch (DAOException e) { log.warn("Could not record finishing state of task '{}'. Task data may remain dangling.", id, e); } } log.debug("State of task {} has been changed to {}", id, state.toString()); } /** * Checks whether the current entry is still valid. * * @param clusterEntry * entry to compare against * @return true if the current entry has not been modified from the outside, * false otherwise */ private boolean isSame(TaskEntry clusterEntry) { boolean isScheduledHere = entry.getRuntimeId().equals(clusterEntry.getRuntimeId()); boolean sameState = (entry.getState() == clusterEntry.getState()); boolean sameContext = (entry.getTaskContextId().equals(clusterEntry.getTaskContextId())); return (isScheduledHere && sameState && sameContext); } /** * * Adds necessary debug information to the cluster. * * @param debugPort * debug port * @param suspended * whether the task has been started suspended */ public void setDebug(int debugPort, boolean suspended) { DebugAssistant debugAssistant = new DebugAssistant(ctx); debugAssistant.addSuspendedTask(id, debugPort, suspended); } /** * Adds command line arguments information to the entry * * @param taskArguments * task command line arguments */ private void setTaskEntryArgs(Collection<String> taskArguments) { TaskEntry.Args args = new TaskEntry.Args(); args.getArg().addAll(taskArguments); entry.setArgs(args); } }