package cz.cuni.mff.d3s.been.taskapi; import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cz.cuni.mff.d3s.been.annotation.NotThreadSafe; import cz.cuni.mff.d3s.been.mq.MessagingException; import cz.cuni.mff.d3s.been.socketworks.NamedSockets; import cz.cuni.mff.d3s.been.socketworks.twoway.Reply; import cz.cuni.mff.d3s.been.socketworks.twoway.ReplyType; import cz.cuni.mff.d3s.been.socketworks.twoway.RequestException; import cz.cuni.mff.d3s.been.socketworks.twoway.Requestor; import cz.cuni.mff.d3s.been.task.checkpoints.CheckpointRequest; import cz.cuni.mff.d3s.been.task.checkpoints.CheckpointRequestType; import cz.cuni.mff.d3s.been.util.JsonException; /** * Sends checkpoint requests of tasks to its Host Runtime. * <p/> * The CheckpointController provides checkpoint REQ-REP semantics for tasks. * Requests are handled by the corresponding Host Runtime. The methods block * until the request is handled. The blocking time is unbounded for some * requests (use timeout if you don't want to block for arbitrary long time). * <p/> * Calls are not thread safe. Create a requester for each thread (and inside the * thread) which might want to issue request. * <p/> * <p/> * After you are done, {@link #close()} must be called. Otherwise the process * will not terminate. * * @author Martin Sixta */ @NotThreadSafe public class CheckpointController implements AutoCloseable { private final Requestor requestor; /** * logging */ private static final Logger log = LoggerFactory.getLogger(CheckpointController.class); /** * Creates a new CheckpointController. Each thread must create its own * CheckpointController, the class is not thread safe. Also the object should * be created in the thread that wants to use it. */ private CheckpointController(Requestor requestor) { this.requestor = requestor; } /** * Creates default checkpoint controller. * * @return default checkpoint controller * * @throws MessagingException * when connection cannot be established */ public static CheckpointController create() throws MessagingException { return create(NamedSockets.TASK_CHECKPOINT_0MQ.getConnection()); } /** * Creates controller. * * @param connection * where to request requests * @return checkpoint controller * @throws MessagingException * when connection cannot be established */ public static CheckpointController create(String connection) throws MessagingException { final Requestor requestor = Requestor.create(connection); return new CheckpointController(requestor); } /** * Closes the requestor. No further request will be handled by the object. * <p/> * Must be called to release associated resources. Failing to do so will hand * the process on exit. */ public void close() throws MessagingException { requestor.close(); } /** * Sets value of a checkpoint. * * @param checkPointName * name of the check point to set * @param value * value of the check point * @throws RequestException * when the request fails */ public void checkPointSet(String checkPointName, String value) throws RequestException { CheckpointRequest request = new CheckpointRequest(CheckpointRequestType.SET, checkPointName, value); Reply reply = send(request); // TODO handle error reply better if (reply.getReplyType() != ReplyType.OK) { throw new RequestException("Address set failed"); } } /** * Send a premade {@link CheckpointRequest} * * @param request * Request to send * * @return The reply, or <code>null</code> if anything goes awry */ public Reply request(CheckpointRequest request) { return send(request); } /** * Retrieves value of a check point. * * @param name * name of the check point * @return value of the check point * @throws RequestException * when the request fails */ public String checkPointGet(String name) throws RequestException { CheckpointRequest request = new CheckpointRequest(CheckpointRequestType.GET, name); Reply reply = send(request); if (reply.getReplyType() != ReplyType.OK) { log.error(reply.getValue()); throw new RequestException("Address set failed"); } return reply.getValue(); } /** * Waits for a check point with timeout. The method will return once the * checkpoint has a value or the request timeouts. * * @param name Name of the checkpoint * @param timeout * Timeout in seconds * * @return value of the check point * * @throws RequestException * when the request fails * @throws TimeoutException * when the request timeouts */ public String checkPointWait(String name, long timeout) throws RequestException, TimeoutException { CheckpointRequest request = new CheckpointRequest(CheckpointRequestType.WAIT, name, timeout); Reply reply = send(request); if (reply.getReplyType() == ReplyType.ERROR) { String value = reply.getValue(); if (value.equals("TIMEOUT")) { throw new TimeoutException(String.format("Wait for %s timed out", name)); } else { throw new RequestException(String.format("Wait for %s failed", name)); } } return reply.getValue(); } /** * Waits until a check point is set. * * @param name * Name of the check point * @return Value of the checkpoint * @throws RequestException * when the request fails */ public String checkPointWait(String name) throws RequestException { try { return checkPointWait(name, 0); } catch (TimeoutException e) { // should not time out throw new RequestException(e); } } /** * Waits for count down of a Latch with timeout. * * @param name * name of the latch * @param timeout * timeout in seconds * @throws RequestException * when the request fails * @throws TimeoutException * when the request timeouts */ public void latchWait(String name, long timeout) throws RequestException, TimeoutException { CheckpointRequest request = new CheckpointRequest(CheckpointRequestType.LATCH_WAIT, name, timeout); Reply reply = send(request); if (reply.getReplyType() == ReplyType.ERROR) { String value = reply.getValue(); if (value.equals("TIMEOUT")) { throw new TimeoutException(String.format("Wait for %s count down timed out", name)); } else { throw new RequestException(String.format("Wait for %s count down failed", name)); } } } /** * Waits for count down of a Latch. * * @param name * name of the latch * @throws RequestException * when the request fails */ public void latchWait(String name) throws RequestException { try { latchWait(name, 0); } catch (TimeoutException e) { // should not time out throw new RequestException(e); } } /** * Counts down a Latch. * * @param name * name of the latch * @throws RequestException * when the request fails */ public void latchCountDown(String name) throws RequestException { CheckpointRequest request = new CheckpointRequest(CheckpointRequestType.LATCH_DOWN, name, null); Reply reply = send(request); if (reply.getReplyType() != ReplyType.OK) { throw new RequestException(String.format("Count down of %s failed", name)); } } /** * Sets value of a latch. * <p/> * The desired value must be set before any attempt to count it down. * <p/> * The count down can be reset but only if the value reaches zero. * * @param name * name of the latch * @param count * desired count * @throws RequestException * when the request fails */ public void latchSet(String name, int count) throws RequestException { CheckpointRequest request = new CheckpointRequest(CheckpointRequestType.LATCH_SET, name, Integer.toString(count)); Reply reply = send(request); if (reply.getReplyType() != ReplyType.OK) { log.error(reply.getValue()); throw new RequestException("Wait failed"); } } /** * Sends an arbitrary request, waits for reply. * <p/> * The call will block until the request is handled by the Host Runtime. * * @param request * a request * @return reply for the request */ private Reply send(CheckpointRequest request) { request.fillInTaskAndContextId(); final String replyString = requestor.request(request.toJson()); try { return Reply.fromJson(replyString); } catch (JsonException e) { log.error("Failed to deserialize reply {}", replyString, e); return null; } } }