package cz.cuni.mff.d3s.been.cluster.action;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryListener;
import com.hazelcast.core.IMap;
import cz.cuni.mff.d3s.been.cluster.context.ClusterContext;
import cz.cuni.mff.d3s.been.core.task.TaskContextEntry;
import cz.cuni.mff.d3s.been.core.task.TaskContextState;
import cz.cuni.mff.d3s.been.socketworks.twoway.Replies;
import cz.cuni.mff.d3s.been.socketworks.twoway.Reply;
import cz.cuni.mff.d3s.been.socketworks.twoway.Request;
/**
* An {@link Action} that handles a request for waiting until a specified task
* context is finished.
*
* @author Kuba Brecka
*/
public class ContextWaitAction implements Action {
/** slf4j logger */
private static final Logger log = LoggerFactory.getLogger(ContextWaitAction.class);
/** the request to handle */
private final Request request;
/** BEEN cluster instance */
private final ClusterContext ctx;
/** a blocking queue which is used for the waiting operation */
BlockingQueue<TaskContextEntry> queue = new LinkedBlockingQueue<>();
/**
* Default constructor, creates the action with the specified request and
* cluster context.
*
* @param request
* the request to handle
* @param ctx
* the cluster context
*/
public ContextWaitAction(Request request, ClusterContext ctx) {
this.request = request;
this.ctx = ctx;
}
/**
* A helper class which implements a listener for a specified Hazelcast map
* entry and adds it into the blocking queue when the event occurs.
*/
class MapWaiter implements EntryListener<String, TaskContextEntry> {
@Override
public void entryAdded(EntryEvent<String, TaskContextEntry> event) {}
@Override
public void entryRemoved(EntryEvent<String, TaskContextEntry> event) {
queue.add(event.getValue());
}
@Override
public void entryUpdated(EntryEvent<String, TaskContextEntry> event) {
if (isContextDone(event.getValue())) {
queue.add(event.getValue());
}
}
@Override
public void entryEvicted(EntryEvent<String, TaskContextEntry> event) {
queue.add(event.getValue());
}
}
@Override
public Reply handle() {
String key = request.getValue();
Reply reply = null;
final MapWaiter waiter = new MapWaiter();
IMap<String, TaskContextEntry> iMap = ctx.getTaskContexts().getTaskContextsMap();
iMap.addEntryListener(waiter, key, true);
TaskContextEntry value = iMap.get(key);
boolean timeout = false;
// TODO states...
if (value == null || !isContextDone(value)) {
try {
if (request.getTimeout() <= 0) {
value = queue.take();
} else {
value = queue.poll(request.getTimeout(), TimeUnit.MILLISECONDS);
if (value == null) {
timeout = true;
}
}
} catch (InterruptedException e) {
log.warn("Poll interrupted", e);
}
}
if (value == null) {
if (timeout) {
reply = Replies.createErrorReply("TIMEOUT");
} else {
reply = Replies.createErrorReply("Unknown error");
}
} else {
reply = Replies.createOkReply(value.getContextState().toString());
}
iMap.removeEntryListener(waiter);
queue.clear();
return reply;
}
/**
* Checks whether the task context is already finished (or failed) or not.
*
* @param entry
* the task context entry to check
* @return true if the task context is finished or failed, false otherwise
*/
// TODO move to an utility class
private boolean isContextDone(TaskContextEntry entry) {
return entry.getContextState() == TaskContextState.FINISHED || entry.getContextState() == TaskContextState.FAILED;
}
}