org.piax.gtrans.ov.sg.SGMessagingFramework Maven / Gradle / Ivy
Show all versions of piax-compat Show documentation
/*
* SGMessagingFramework.java - SGMessagingFramework implementation of SkipGraph.
*
* Copyright (c) 2015 Kota Abe / PIAX development team
*
* You can redistribute it and/or modify it under either the terms of
* the AGPLv3 or PIAX binary code license. See the file COPYING
* included in the PIAX package for more in detail.
*
* $Id: MSkipGraph.java 1160 2015-03-15 02:43:20Z teranisi $
*/
package org.piax.gtrans.ov.sg;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.piax.common.Endpoint;
import org.piax.gtrans.RPCException;
import org.piax.gtrans.ov.Link;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* message framework for skip graphs.
*
* this class provides features to send a message, which is a subclass of
* {@link SGRequestMessage}, to a remote node and get an ack and a reply
* message, which is a subclass of {@link SGReplyMessage}. when a reply message
* arrives, SGRequestMessage#onReceivingReply(SkipGraph, SGReplyMessage)
* is called. if ack message timeouts,
* SGRequestMessage#onTimeOut(SkipGraph) is called.
*
* the destination of the ack message and the reply message may be different
* node.
*/
public class SGMessagingFramework {
/*--- logger ---*/
private static final Logger logger = LoggerFactory
.getLogger(SGMessagingFramework.class);
public final static int DUMMY_MSGID = 0;
public static int ACK_TIMEOUT_THRES = 19900;
public static int ACK_TIMEOUT_TIMER = ACK_TIMEOUT_THRES + 100;
/** for storing on-going request messages */
// FIXME: Ackを受信した後,Replyが受信されない場合,永遠に削除されない.
private final Map> msgStore =
new ConcurrentHashMap>();
private AtomicInteger nextId = new AtomicInteger(0);
E myLocator;
final private SkipGraph sg;
public static int MSGSTORE_EXPIRATION_TASK_PERIOD = 60 * 1000; // 60sec
TimerTask cleaner;
public SGMessagingFramework(SkipGraph sg) {
this.sg = sg;
this.myLocator = sg.myLocator;
sg.timer.schedule(cleaner = new TimerTask() {
public void run() {
removeExpiredFromMsgStore();
}
}, MSGSTORE_EXPIRATION_TASK_PERIOD, MSGSTORE_EXPIRATION_TASK_PERIOD);
}
private void removeExpiredFromMsgStore() {
final long now = System.currentTimeMillis();
for (Iterator it = msgStore.keySet().iterator(); it.hasNext();) {
int msgId = it.next();
SGRequestMessage entry = msgStore.get(msgId);
if (entry.timestamp < (now - entry.expire)) {
logger.debug("removing expired: {}", msgId);
it.remove();
}
}
}
public void fin() {
cleaner.cancel();
}
int getNextMsgId() {
return nextId.incrementAndGet();
}
private void send(E dst, final SGRequestMessage msg,
boolean waitForAck) {
prepareReceivingReply(msg, waitForAck, true);
// call remote
logger.debug("* request {} ===> {}: {}", myLocator, dst, msg);
try {
SkipGraphIf stub = sg.getStub(dst);
stub.requestMsgReceived(msg);
} catch (RPCException e) {
logger.info("", e);
}
}
/**
* prepare for receiving a reply for this message.
* see {@link #prepareReceivingReply(SGRequestMessage, boolean, boolean)}.
*/
private void prepareReceivingReply(final SGRequestMessage msg,
boolean expectAck) {
prepareReceivingReply(msg, expectAck, false);
}
/**
* prepare for receiving a reply for this message.
* register this message to {@link SGMessagingFramework#msgStore} and
* schedule a timer for ack timeout.
*
* @param msg
* @param expectAck
* timeout for the Ack in msec
* @param callOnTimeout
* if true, {@code SGRequestMessage#onTimeOut(SkipGraph)} is
* called on Ack timeout.
*/
private void prepareReceivingReply(final SGRequestMessage msg,
boolean expectAck, final boolean callOnTimeout) {
if (msg.timeoutTask != null) {
throw new Error("sent twice: " + this);
}
// register this message
assert msg.msgId != DUMMY_MSGID;
msgStore.put(msg.msgId, msg);
logger.trace("prepareReceivingReply: {}: {}", sg.toStringShort(), msg);
if (!expectAck) {
assert !callOnTimeout;
return;
}
// schedule timer
msg.timeoutTask = new TimerTask() {
@Override
public void run() {
// invoke instantFix
logger.debug("timeout, waiting ack for {}", msg);
msgStore.remove(msg.msgId);
logger.trace("unregister by timeout: {}: {}",
sg.toStringShort(), msg);
if (callOnTimeout) {
msg.onTimeOut(sg);
}
}
};
sg.timer.schedule(msg.timeoutTask, ACK_TIMEOUT_TIMER);
}
public void requestMsgReceived(SGRequestMessage req) {
// restore
req.sgmf = this;
// mark this message is not for sending.
req.isRecvdInstance = true;
// send ack message
// TODO: implement delayed ack to save network bandwidth
logger.debug("* ack {} ===> {}: msgId = {}", myLocator, req.sender,
req.msgId);
try {
SkipGraphIf stub = sg.getStub((E) req.sender);
stub.ackReceived(req.msgId);
} catch (RPCException e) {
logger.info("", e);
}
logger.debug("execute, msgId = {}", req.msgId);
req.execute(sg);
}
public void ackReceived(int msgId) {
String h = "ackReceived(" + myLocator + ")";
SGRequestMessage req;
req = msgStore.get(msgId);
if (req == null) {
// this may happen if the corresponding reply message has been
// received before receiving this ack.
logger.info("{}: no corresponding request (maybe ok): msgId = {}",
h, msgId);
} else {
synchronized (req) {
logger.debug("{}: acked: {}", h, req);
if (req.timeoutTask != null) {
req.timeoutTask.cancel();
}
req.ackReceived = true;
if (req.isDirectReturn) {
// clear msgStore because this node does not receive
// the reply.
msgStore.remove(msgId);
logger.trace("unregister by Ack: {}: {}", sg.toStringShort(),
req);
}
}
}
}
public void replyMsgReceived(SGReplyMessage repl) {
String h = "replyMsgReceived(" + myLocator + ")";
// pickup SGRequestMessage from msgStore using msgId
SGRequestMessage req;
req = msgStore.get(repl.replyId);
if (req == null) {
logger.info("{}: no corresponding request (maybe ok): replyId={}", h,
repl.replyId);
} else {
assert req.mayReceiveReply();
// restore msgf (transient)
req.sgmf = sg.sgmf;
// XXX: locking gap ok?
synchronized (req) {
logger.debug("{}: receive reply, replyId={}", h, repl.replyId);
if (req.timeoutTask != null) {
req.timeoutTask.cancel();
}
boolean removeOK = req.onReceivingReply(sg, repl);
logger.debug("{}: removeOK = {}", h, removeOK);
if (removeOK) {
msgStore.remove(repl.replyId);
logger.trace("unregister by reply: {}: {}",
sg.toStringShort(), req);
logger.debug("{}: msgStore = {}", h, msgStore);
}
}
}
}
/**
* a base class for a request message.
*
* @author k-abe
*/
public static abstract class SGRequestMessage implements Serializable {
private static final long serialVersionUID = 1L;
final E sender;
Link receiver;
final int msgId;
final E replyTo;
final int replyId;
final boolean isDirectReturn;
final transient boolean isRoot;
final int expire;
transient TimerTask timeoutTask;
transient SGMessagingFramework sgmf;
transient long timestamp;
transient boolean ackReceived = false;
transient boolean isRecvdInstance = false;
/**
* create a SGRequestMessage instance.
*
* @param sgmf the messaging framework.
* @param isRoot indicate whether this message is root.
* @param isDirectReturn
* true if the reply for this message is directly sent to
* the root node.
* @param replyTo
* if non-null, reply is sent to this node. otherwise, reply
* is sent to the sender node.
* @param replyId
* used only when replyTo != null and specify the replyId at
* the root node.
* @param expire the expiration time.
*/
public SGRequestMessage(SGMessagingFramework sgmf, boolean isRoot,
boolean isDirectReturn, E replyTo, int replyId, int expire) {
assert isDirectReturn == (replyTo != null);
this.sgmf = sgmf;
this.sender = sgmf.myLocator;
this.isRoot = isRoot;
this.isDirectReturn = isDirectReturn;
this.msgId = sgmf.getNextMsgId();
this.expire = expire;
if (isDirectReturn) {
assert replyTo != null;
this.replyTo = replyTo;
if (isRoot) {
assert replyId == DUMMY_MSGID;
this.replyId = msgId;
} else {
assert replyId != DUMMY_MSGID;
this.replyId = replyId;
}
} else {
assert replyTo == null;
assert replyId == DUMMY_MSGID;
this.replyTo = sender;
this.replyId = this.msgId;
}
if (isRoot && isDirectReturn) {
/*
* in direct return mode, register this message for receiving
* replies. this message is unregistered when sufficient
* replies are received.
*/
sgmf.prepareReceivingReply(this, false);
}
}
/**
* create a SGRequestMessage instance.
*
* @param msg copy source
*/
public SGRequestMessage(SGRequestMessage msg) {
this.sgmf = msg.sgmf;
this.sender = sgmf.myLocator;
this.isRoot = msg.isRoot;
this.isDirectReturn = msg.isDirectReturn;
this.msgId = sgmf.getNextMsgId();
this.replyTo = msg.replyTo;
this.replyId = msg.replyId;
this.expire = msg.expire;
if (isRoot && isDirectReturn) {
sgmf.prepareReceivingReply(this, false);
}
}
/**
* send this message to the specified destination.
*
* when this message is arrived at the destination node,
* an ACK message is automatically sent to the sender node and
* {@link #execute(SkipGraph)} is called at the destination node.
*
* when we receive a reply message, {@link #onReceivingReply}
* is called. (note that when isDirectReturn
* == true, we do not receive any reply message in non-root nodes).
*
* if we do not receive an ACK message
* within {@link SGMessagingFramework#ACK_TIMEOUT_THRES},
* {@link #onTimeOut(SkipGraph)} is called.
*
* Note:
* the message is stored in SGMessagingFramework#msgStore
* for handling ACK and reply messages.
*
* the message is removed when (1) ACK is received (if
* isDirectReturn==true), or (2) a reply message is received
* (otherwise).
*
* @param dst destination node
*/
@SuppressWarnings("unchecked")
public void send(Link dst) {
assert !isRecvdInstance;
receiver = dst;
timestamp = System.currentTimeMillis();
sgmf.send((E) dst.addr, this, true);
}
/**
* returns if we have not received any ACK within
* {@link SGMessagingFramework#ACK_TIMEOUT_THRES}.
*
* @return true if ACK is timed-out.
*/
public boolean isAckTimedOut() {
if (ackReceived) {
return false;
}
return (timestamp != 0 && (System.currentTimeMillis() - timestamp >
ACK_TIMEOUT_THRES));
}
/**
* このメッセージはsendしないが,replyは受信するという場合に,受信に備えておく.
* メッセージを{@link #send(Link)}以外の手段によって伝送した場合に使用する.
*/
public void prepareReceivingReply() {
assert !isRecvdInstance;
if (mayReceiveReply()) {
logger.debug("prepareForReply");
sgmf.prepareReceivingReply(this, false);
}
}
public boolean mayReceiveReply() {
return !isDirectReturn || isRoot;
}
/**
* this method is called when this message is received at the receiver
* node.
*
* @param sg an instance of SkipGraph
*/
public abstract void execute(SkipGraph sg);
/**
* this method is called when a reply message is received at the sender
* node.
*
* @param sg an instance of SkipGraph
* @param reply the reply message
* @return true if this instance is no longer required at the sender
* node.
*/
public abstract boolean onReceivingReply(SkipGraph sg,
SGReplyMessage reply);
/**
* this method is called when the ack for this message is timed out.
*
* @param sg an instance of SkipGraph
*/
public abstract void onTimeOut(SkipGraph sg);
}
/**
* a base class for a reply message.
*
* @author k-abe
*/
public static abstract class SGReplyMessage implements Serializable {
private static final long serialVersionUID = 1L;
/*--- logger ---*/
private static final Logger logger = LoggerFactory
.getLogger(SGReplyMessage.class);
final E sender;
final int replyId;
final transient SkipGraph sg;
final transient E receiver;
/**
* compose a reply message against the specified SGRequestMessage.
*
* @param sg Skip Graph instance
* @param received the SGRequestMessage for which this reply message
* is sent
*/
public SGReplyMessage(SkipGraph sg, SGRequestMessage received) {
this.sender = sg.myLocator;
this.receiver = received.replyTo;
this.sg = sg;
this.replyId = received.replyId;
logger.debug("SGReplyMessage: replyId = " + replyId);
}
/**
* send a reply message. the destination node is automatically
* determined from the request message.
*/
public synchronized void reply() {
logger.debug("* reply {} ===> {}: replyId = {}", sender, receiver,
replyId);
try {
SkipGraphIf stub = sg.getStub(receiver);
stub.replyMsgReceived(this);
} catch (RPCException e) {
logger.info("", e);
}
}
}
}