package cz.cuni.mff.d3s.been.manager.action;
import static cz.cuni.mff.d3s.been.core.task.TaskState.SCHEDULED;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.concurrent.TimeoutException;
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.protocol.messages.RunTaskMessage;
import cz.cuni.mff.d3s.been.core.task.TaskEntries;
import cz.cuni.mff.d3s.been.core.task.TaskEntry;
import cz.cuni.mff.d3s.been.core.task.TaskState;
import cz.cuni.mff.d3s.been.manager.selector.NoRuntimeFoundException;
import cz.cuni.mff.d3s.been.manager.selector.RuntimeSelectors;
/**
*
* Action which tries to schedule a task on a Host Runtime.
*
* The action may not succeed (i.e. no suitable Host Runtime is found). In such
* case the task is put into WAITING state or left alone (if serious problem
* arises).
*
* @author Martin Sixta
*/
final class ScheduleTaskAction implements TaskAction {
/** default lock timeout value */
private static final int LOCK_TIMEOUT = 60;
/** logging */
private static Logger log = LoggerFactory.getLogger(ScheduleTaskAction.class);
/** map with tasks */
final IMap<String, TaskEntry> map;
/** tasks utility class */
final Tasks tasks;
/** connection to the cluster */
private final ClusterContext ctx;
/** the task to schedule */
private TaskEntry entry;
/**
* Creates a new action that schedules tasks
*
* @param ctx
* connection to the cluster
* @param entry
* task to schedule
*/
public ScheduleTaskAction(final ClusterContext ctx, final TaskEntry entry) {
this.ctx = ctx;
this.entry = entry;
this.tasks = ctx.getTasks();
this.map = tasks.getTasksMap();
}
@Override
public void execute() {
final String id = entry.getId();
log.debug("Received new task to schedule {}", id);
try {
// 1) Find suitable Host Runtime
String receiverId = RuntimeSelectors.fromEntry(entry, ctx).select();
// 2) Lock the entry
TaskEntry entryCopy = map.tryLockAndGet(id, LOCK_TIMEOUT, SECONDS);
// check that we are processing unchanged entry
if (!areEqual(entry, entryCopy)) {
map.unlock(id);
return;
}
// 3) change the entry
// Update content of the entry
TaskEntries.setState(entry, SCHEDULED, "Task scheduled on %s", receiverId);
entry.setRuntimeId(receiverId);
// 4) Update entry
tasks.putTask(entry);
map.unlock(id);
// 5) Send a message to the runtime
ctx.getTopics().publishInGlobalTopic(newRunTaskMessage());
log.debug("Task {} scheduled on {}", id, receiverId);
} catch (NoRuntimeFoundException e) {
String msg = String.format("No runtime found for task %s", entry.getId());
log.debug(msg);
stashTask("No suitable host found");
} catch (TimeoutException e) {
log.warn("Could not lock task {} in {}. Will try later if needed.", id, LOCK_TIMEOUT);
// will get to it later
} finally {
if (map.isLocked(id)) {
try {
map.unlock(id);
} catch (IllegalMonitorStateException e) {
// quell
}
}
}
}
/**
* Sets the state of the task to WAITING to be rescheduled when appropriate
* event happens.
*/
private void stashTask(String msg) {
final String id = entry.getId();
map.lock(entry.getId());
try {
TaskEntry entryCopy = map.get(id);
if (!areEqual(entry, entryCopy)) {
return;
}
if (entry.getState() != TaskState.WAITING) {
TaskEntries.setState(entry, TaskState.WAITING, msg);
tasks.putTask(entry);
}
} finally {
map.unlock(id);
}
}
/**
* Auxiliary which creates a message to be send to a selected HostRuntime
*
* @return Run task message
*/
private RunTaskMessage newRunTaskMessage() {
String receiverId = entry.getRuntimeId();
String taskId = entry.getId();
return new RunTaskMessage(receiverId, taskId);
}
/**
* Checks equality of two task entries
*
* @param entry1
* first entry
* @param entry2
* second entry
* @return true if entries are equal, false otherwise
*/
private boolean areEqual(TaskEntry entry1, TaskEntry entry2) {
return entry1.equals(entry2);
}
}