/**
* Fortika - Robust Group Communication
* Copyright (C) 2002-2006 Sergio Mena de la Cruz (EPFL) (sergio.mena@@epfl.ch)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Author: Olivier Rütti
*/
package groupcomm.common.order;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.logging.Logger;
import uka.transport.Transportable;
import framework.Constants;
import framework.GroupCommEventArgs;
import framework.GroupCommException;
import framework.GroupCommMessage;
import framework.PID;
import framework.libraries.DefaultSerialization;
import framework.libraries.FlowControl;
import framework.libraries.Trigger;
import framework.libraries.serialization.TBoolean;
import framework.libraries.serialization.THashMap;
import framework.libraries.serialization.THashSet;
import framework.libraries.serialization.TInteger;
import framework.libraries.serialization.TLinkedList;
import framework.libraries.serialization.TList;
import framework.libraries.serialization.TMap;
public class CausalOrderImpl {
// Identifier of Causal Ordering Messages
public static final int CAUSAL_ORDER_SEND = 1;
public static final int CAUSAL_ORDER_ACK = 3;
// ID of the next messages to be sent
private CausalOrderMessageID causalID;
// Messages currently sent
private TMap buffer;
// Messages not received but acknowledged
private TMap bufferAck;
// Messages to be sent
private TLinkedList outqueue;
// Trigger class for events routing
private Trigger trigger;
// Local ID
private PID myself;
// FlowControl
private FlowControl fc;
private int fc_key;
// Flow Control Settings
private static int MAX_PENDING_MESSAGES = 100;
private static final Logger logger = Logger.getLogger(CausalOrderImpl.class
.getName());
private static class TriggerItem {
public int type;
public GroupCommEventArgs args;
public TriggerItem(int type, GroupCommEventArgs args) {
this.type = type;
this.args = args;
}
}
/**
* Constructor.
*
* @@param CausalOrder
* object of a framework protocol based class, which ensure event
* routing for this protocol.
*/
public CausalOrderImpl(Trigger CausalOrder, FlowControl fc, PID myself) {
logger.entering("CausalOrderImpl", "<constr>");
this.trigger = CausalOrder;
this.fc = fc;
this.myself = myself;
this.causalID = new CausalOrderMessageID(myself, 0);
this.buffer = new THashMap();
this.bufferAck = new THashMap();
this.outqueue = new TLinkedList();
logger.exiting("CausalOrderImpl", "<constr>");
}
/**
* Handler for the <i>Init</i> event. </br> It sends the list of known
* processes to the lower layer allowing them to communicate with us
*
* @@param ev
* <dl>
* <dt> arg1 : Set[PID] </dt>
* <dd> List of processes for broadcasting </dd>
* </dl>
*
* @@throws GroupCommException
* @@throws IOException
* @@throws ClassNotFoundException
*/
public void handleInit(GroupCommEventArgs ev) throws GroupCommException,
IOException, ClassNotFoundException {
logger.entering("CausalOrderImpl", "handleInit");
TList p = (TList) ev.removeFirst();
// Get a kex for the FlowControl
this.fc_key = fc.getFreshKey();
// Look for duplicate processes in the group
for (int i = 0; i < p.size(); i++) {
for (int j = i + 1; j < p.size(); j++)
if (p.get(i).equals(p.get(j)))
throw new GroupCommException("Process" + p.get(i)
+ " appears more than once in the group.");
}
// join-remove
GroupCommEventArgs jrl = new GroupCommEventArgs();
jrl.addLast(new THashSet(p)); // join
jrl.addLast(new THashSet()); // remove
trigger.trigger(Constants.JOINREMOVELIST, jrl);
logger.exiting("CausalOrderImpl", "handleInit");
}
/**
* The handler for the <i>CausalOrderSend</i> event. <br/> It send the message to
* the specificied process while respecting CausalOrder order. The message are
* delivered in the rder they were sent. <br/>
*
* @@param ev
* <dl>
* <dt> arg1: GroupCommMessage </dt>
* <dd> The message </dd>
* <dt> arg2: PID </dt>
* <dd> The destination </dd>
* </dl>
* @@throws GroupCommException
* @@throws IOException
* @@throws ClassNotFoundException
*/
public void handleCausalOrderSend(GroupCommEventArgs ev)
throws GroupCommException {
logger.entering("CausalOrderImpl", "handleCausalOrdersend");
LinkedList toTrigger = new LinkedList();
if (buffer.isEmpty()) {
proceedSendMessage(toTrigger, ev);
proceedWithTrigger(toTrigger);
} else {
outqueue.addLast(ev);
if ((outqueue.size()+buffer.size()) > MAX_PENDING_MESSAGES)
fc.block(fc_key);
}
logger.exiting("CausalOrderImpl", "handleCausalOrdersend");
}
/**
* The handler for the <i>Pt2PtDeliver</i> event. <br/> When we recieve a
* message from the Reliable communication layer, we have to resent the
* message to all the receipents, if it's the first time it arrives. That's
* the R-Broadcast part of the protocol. It launch a consensus too.
*
* @@param ev
* <dl>
* <dt> arg1: GroupCommMessage (id::m) </dt>
* <dd> The message, with an id </dd>
* <dt> arg2: PID </dt>
* <dd> Source PID </dd>
* </dl>
*/
public void handlePt2PtDeliver(GroupCommEventArgs ev) throws GroupCommException {
logger.entering("CausalOrderImpl", "handlePt2PtDeliver");
// msg = id::m
// msgClone = id::m
GroupCommMessage msg = (GroupCommMessage) ev.get(0);
PID source = (PID) ev.get(1);
int type = ((TInteger) msg.removeFirst()).intValue();
CausalOrderMessageID cID = (CausalOrderMessageID) msg.removeFirst();
LinkedList toTrigger = new LinkedList();
switch (type) {
case CAUSAL_ORDER_SEND:
proceedMessage(toTrigger, msg, cID, source);
break;
case CAUSAL_ORDER_ACK:
proceedAck(toTrigger, cID, source);
break;
default:
System.err.println("SHOULD NEVER HAPPEN");
}
proceedWithTrigger(toTrigger);
logger.exiting("CausalOrderImpl", "handlePt2PtDeliver");
}
// Execute all the trigger in list of TriggerItem toTrigger
private void proceedWithTrigger(LinkedList toTrigger) {
while (!toTrigger.isEmpty()) {
TriggerItem tItem = (TriggerItem) toTrigger.removeFirst();
trigger.trigger(tItem.type, tItem.args);
}
}
/**
* Send the message contains in GroupCommEventArgs
*/
private void proceedSendMessage(LinkedList toTrigger, GroupCommEventArgs ev)
throws GroupCommException {
GroupCommMessage gm = (GroupCommMessage) ev.get(0);
TList destTmp = new TLinkedList((TList) ev.get(1));
TList destList = new TLinkedList((TList) ev.get(1));
CausalOrderMessageID cID = causalID.nextId();
buffer.put(cID, destList);
gm.addFirst(ev.remove(1));
gm.addFirst(cID);
gm.addFirst(new TInteger(CAUSAL_ORDER_SEND));
while (!destTmp.isEmpty()) {
GroupCommEventArgs evSent = new GroupCommEventArgs();
GroupCommMessage sent;
PID dest = (PID) destTmp.remove(0);
if (dest.equals(myself)){
destList.remove(myself);
try {
sent = (GroupCommMessage) deepClone(gm);
} catch (Exception ex) {
throw new GroupCommException("Unable to send "+gm.toString());
}
// Deliver the message
GroupCommEventArgs evDel = new GroupCommEventArgs();
// Remove type::cID::dest from message sent
sent.tunpack();
sent.tunpack();
sent.tunpack();
evDel.addLast(sent);
evDel.addLast(myself);
toTrigger.addLast(new TriggerItem(Constants.CODELIVER, evDel));
// Send an ack to all processes in dest
int sizeDestList = destList.size();
for (int i=0; i<sizeDestList; i++){
GroupCommEventArgs evAck = new GroupCommEventArgs();
GroupCommMessage ack = new GroupCommMessage();
ack.addFirst(new TInteger(CAUSAL_ORDER_ACK));
ack.addLast(cID);
evAck.addLast(ack);
evAck.addLast(destList.get(i));
evAck.addLast(new TBoolean(false));
toTrigger.addLast(new TriggerItem(Constants.PT2PTSEND, evAck));
}
} else {
// msg
sent = gm.cloneGroupCommMessage();
evSent.addLast(sent);
evSent.addLast(dest);
evSent.addLast(new TBoolean(false));
toTrigger.addLast(new TriggerItem(Constants.PT2PTSEND, evSent));
}
}
}
/**
* Proceed acknolegdement
*/
private void proceedAck(LinkedList toTrigger, CausalOrderMessageID cID, PID source) throws GroupCommException {
TLinkedList dst = (TLinkedList) buffer.get(cID);
// If the message is already known
// remove the source of the ack and if
// we received an ack from all p in dest
// proceed with the next message
if (dst!=null) {
dst.remove(source);
if (dst.isEmpty()) {
buffer.remove(cID);
if ((buffer.isEmpty()) && (!outqueue.isEmpty())) {
GroupCommEventArgs ev = (GroupCommEventArgs) outqueue
.removeFirst();
proceedSendMessage(toTrigger, ev);
}
}
} else {
// If the message is not already known
// store the ack in bufferAck
// the ack will be processed on reception
// of the message
TLinkedList acked = (TLinkedList) bufferAck.get(cID);
if (acked == null){
acked = new TLinkedList();
bufferAck.put(cID, acked);
}
acked.add(source);
}
}
/**
* Proceed acknolegdement
*/
private void proceedMessage(LinkedList toTrigger, GroupCommMessage msg, CausalOrderMessageID cID,
PID source) {
TLinkedList destList = (TLinkedList) msg.removeFirst();
TLinkedList ackList = (TLinkedList) bufferAck.get(cID);
// Deliver the message
GroupCommEventArgs evDel = new GroupCommEventArgs();
evDel.addLast(msg);
evDel.addLast(source);
toTrigger.addLast(new TriggerItem(Constants.CODELIVER, evDel));
// Send an ack to all processes in dest
int sizeDestList = destList.size();
for (int i=0; i<sizeDestList; i++){
GroupCommEventArgs evAck = new GroupCommEventArgs();
GroupCommMessage ack = new GroupCommMessage();
ack.addFirst(new TInteger(CAUSAL_ORDER_ACK));
ack.addLast(cID);
evAck.addLast(ack);
evAck.addLast(destList.get(i));
evAck.addLast(new TBoolean(false));
toTrigger.addLast(new TriggerItem(Constants.PT2PTSEND, evAck));
}
// Send an ack to the the source if the source is not in the dest
if (!destList.contains(source)){
GroupCommEventArgs evAck = new GroupCommEventArgs();
GroupCommMessage ack = new GroupCommMessage();
ack.addFirst(new TInteger(CAUSAL_ORDER_ACK));
ack.addLast(cID);
evAck.addLast(ack);
evAck.addLast(source);
evAck.addLast(new TBoolean(false));
toTrigger.addLast(new TriggerItem(Constants.PT2PTSEND, evAck));
}
// Update the set of processes that has acknoledged
destList.remove(myself);
if (ackList != null)
while (!ackList.isEmpty())
destList.remove(ackList.removeFirst());
buffer.put(cID, destList);
}
private Transportable deepClone(Transportable o) throws IOException,
ClassNotFoundException {
// TODO: There have to be better ways to do deep-clone!!!
return DefaultSerialization
.unmarshall(DefaultSerialization.marshall(o));
}
/**
* Used for debug
*
* @@param out
* The output stream used for showing infos
*/
public void dump(OutputStream out) {
PrintStream err = new PrintStream(out);
err.println("======== CausalOrderImpl: dump =======");
err.println(" Buffered Messages: " + String.valueOf(buffer));
err.println(" Pending Messages: " + String.valueOf(outqueue));
err.println(" Already received ack: " + String.valueOf(bufferAck));
err.println("==================================");
}
}