/**
* 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
*/
package groupcomm.common.consensus;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import uka.transport.DeepClone;
import uka.transport.MarshalStream;
import uka.transport.Transportable;
import uka.transport.UnmarshalStream;
import framework.Constants;
import framework.GroupCommEventArgs;
import framework.GroupCommException;
import framework.GroupCommMessage;
import framework.PID;
import framework.libraries.Trigger;
import framework.libraries.serialization.TArrayList;
import framework.libraries.serialization.TBoolean;
import framework.libraries.serialization.THashSet;
import framework.libraries.serialization.TInteger;
import framework.libraries.serialization.TLinkedList;
import framework.libraries.serialization.TList;
import framework.libraries.serialization.TSet;
import framework.libraries.serialization.TSortedMap;
import framework.libraries.serialization.TTreeMap;
/**
* Cette classe g�re une ex�cution de ConsensusMR. <br>
*/
public class ConsensusMRExecution implements Transportable {
//TODO: remove implements Transportable
private static final Logger logger =
Logger.getLogger(ConsensusMRExecution.class.getName());
private TSet suspected = new THashSet();
private Trigger trigger;
/**
* Identifiers of ConsensusMR messages
*/
public static final int CONS_PROPOSE = 1;
public static final int CONS_RBCAST = 2;
public static final int CONS_NACK = 3;
/* VARIABLES GLOBALES DE CONSENSUS */
private PID myself;
/**
* The Consensus number
*/
private Transportable k;
/** The serial number of the current round.
* -1 means that the algorithm has not started yet.
* +infinity means that the algorithm has finished
* (with a decision).
*/
private int round = -1;
/** Number of current phase. Can be 1, 2, 3 and 4.
*/
private int phase = -1;
/**le coordinateur courant
*/
private PID coordinator;
/**l'estimation courante du processus
*/
private Transportable estimate;
private Transportable estimateFromC;
// Number of estimates received. Only valid if the process is executing Phase 2.
private int numReceived = 0;
// We received a null value ?
private boolean receivedNullValue;
// Le nombre de messages � recevoir lors de l'attente d'une majorit� de messages.
private int limit;
// Un tableau qui ne contient que l'identifiant de ce processus.
//private PID[] self;
// Un tableau qui contient tous les processus du groupe sauf soi-m�me.
private TList others;
// Un tableau qui contient tous les processus du groupe.
private TList group;
// La liste des messages d�livr�s � cette couche mais non encore trait� car destin�
private TSortedMap pushedBack = new TTreeMap();
/** Compares ConsensusMRMsg with a ContentWithRound,
* according to the round number.
*/
private class ConsensusMRMessage implements Comparable {
public final int round;
public final int type;
public final GroupCommMessage message;
public ConsensusMRMessage(int r, int t, GroupCommMessage m) {
round = r;
type = t;
message = m;
}
public int compareTo(Object o) {
if (o == this) {
return 0;
}
ConsensusMRMessage other = (ConsensusMRMessage) o;
if (round < other.round) {
return -1;
} else if (round > other.round) {
return +1;
} else if (type < other.type) {
return -1;
} else if (type > other.type) {
return +1;
}
return 0;
}
public String toString() {
return new String(
"(| r: "
+ round
+ " type: "
+ type
+ " msg: "
+ message
+ "|)");
}
}
public ConsensusMRExecution(PID myself,
Transportable k,
TSet suspected,
Trigger trigger) {
this.myself = myself;
this.k = k;
this.suspected = suspected;
this.trigger = trigger;
}
/**
* Lance l'�x�cution de consensus.
*/
public void processStart(Transportable proposal, TList group)
throws GroupCommException {
estimate = proposal;
// List of all processes in the group
//this.group = new PID[group.size()];
this.group = new TArrayList();
// List of all processes in the group but myself
this.others = new TArrayList();
for (int i = 0; i < group.size(); i++) { //TODO: Optimize!
PID p = (PID) group.get(i);
this.group.add(p);
if (!p.equals(myself)) {
this.others.add(p);
}
}
// Limit for ack and estimate messages
limit = this.group.size() / 2;
if (round != -1)
throw new GroupCommException("ConsensusMRExecution: Calling start while round != -1!!");
incRound();
}
/**
* R�ception de l'estimation du coordinateur lors de la phase 3.
*/
public void processSuspect(int r, GroupCommMessage m)
throws GroupCommException {
if (r > round){
// The message is for a future round
// We keep it for later handling
pushback(r, CONS_NACK, m);
logger.log(Level.FINE,
"A NACK was pushed back. Current pushed-back messages: {0}",
pushedBack);}
if (r != round)
// The message is not for this round
return;
if (phase == 1){
// I am the coordinator => BAD!
if (myself.equals(coordinator)) {
throw new GroupCommException(
"Unexpected message received in round "
+ r
+ "by coordinator: "
+ m);
}
estimateFromC = null;
sendSuspect();
initReceivedEstimate();
receivedEstimate(null);
} else if (phase == 2){
receivedEstimate(null);
}
}
/**
* R�ception de l'estimation du coordinateur lors de la phase 3.
*/
public void processPropose(int r, GroupCommMessage m)
throws GroupCommException {
if (r > round){
// The message is for a future round
// We keep it for later handling
pushback(r, CONS_PROPOSE, m);
logger.log(Level.FINE,
"An ACK was pushed back. Current pushed-back messages: {0}",
pushedBack);}
if (r != round)
// The message is not for this round
return;
if (phase == 1){
// I am the coordinator => BAD!
if (myself.equals(coordinator)) {
throw new GroupCommException(
"Unexpected message received in round "
+ r
+ "by coordinator: "
+ m);
}
estimateFromC = m.tunpack();
sendPropose();
initReceivedEstimate();
receivedEstimate(estimateFromC);
} else if (phase == 2){
Transportable content = m.tunpack();
receivedEstimate(content);
}
}
/**
* Envoit d'un NACK au coordinateur si et seulement si celui-ci est suspect�.
*/
public void processSuspicion(TSet suspected) throws GroupCommException {
this.suspected = suspected;
//ConsensusMR is not working (either not started yet or already finished)
if (round == Integer.MAX_VALUE || round == -1)
return;
if (!myself.equals(coordinator) && suspected.contains(coordinator)) {
if (phase == 1){
estimateFromC = null;
sendSuspect();
initReceivedEstimate();
receivedEstimate(null);
}
}
}
/**
* Indique si le consensus a d�j� commencer pour ce processus. <br>
*
* @return Ce consensus a d�j� commencer.
*/
public boolean hasStarted() {
return round > -1;
}
/**
* M�thode qui le passage � un round sup�rieur mais aussi
* une grande partie du passage d'une phase � l'autre.
*/
private void incRound() throws GroupCommException {
// Incr�mente le round et change le coordinateur.
round++;
if(round > 1000) System.err.println("WARNING: ConsensusMR took too many rounds!!!");
coordinator = (PID) group.get(round % group.size());
phase = 1;
numReceived = 0;
estimateFromC = null;
// In any case,
// Pushed back messages are not treated if current round is theirs
processPostponed();
if (myself.equals(coordinator)) {
// I am the coordinator
estimateFromC = estimate;
sendPropose();
initReceivedEstimate();
} else if (suspected.contains(coordinator)){
estimateFromC = null;
sendSuspect();
initReceivedEstimate();
receivedEstimate(null);
}
}
// Init Reception
private void initReceivedEstimate()
throws GroupCommException{
numReceived++;
receivedNullValue = false;
phase = 2;
processPostponed();
}
// Received a estimate in phase 2
private void receivedEstimate(Transportable content)
throws GroupCommException{
numReceived++;
if (content == null)
receivedNullValue = true;
if (numReceived > limit){
if (estimateFromC!=null){
estimate = estimateFromC;
if (receivedNullValue)
incRound();
else
broadcastDecision();
} else
incRound();
}
}
// Process message postponed for the current moment
private void processPostponed()
throws GroupCommException{
while (!pushedBack.isEmpty()) {
TInteger rObj = (TInteger) pushedBack.firstKey();
int r = rObj.intValue();
if (r > round){
// Messages for future round, we keep it and quit the loop
logger.exiting("ConsensusExecution", "nextRound");
return;
}
// We remove all messages of round r from the pushed back queue
TLinkedList l = (TLinkedList) pushedBack.remove(rObj);
if(r < round){
logger.log(
Level.FINE,
"Discarding old messages {0} in pushedBack for round {1}",
new Object[]{ l, rObj} );
} else { // r == round
logger.log(
Level.FINE,
"Processing messages {0} in pushedBack for round {1}",
new Object[]{ l, rObj} );
Iterator it = l.iterator();
while(it.hasNext()){
GroupCommMessage m = (GroupCommMessage) it.next();
int type = ((TInteger)m.tunpack()).intValue();
switch (type) {
case CONS_NACK :
processSuspect(r, m);
break;
case CONS_PROPOSE :
processPropose(r, m);
break;
default :
throw new GroupCommException("Weird message type "
+ type
+ " in pushed back set!");
}
}
}
}
}
private void pushback(int r, int type, GroupCommMessage m) {
logger.entering("ConsensusExecution", "pushback");
TInteger rObj = new TInteger(r);
TLinkedList l = (TLinkedList) pushedBack.get(rObj);
if(l == null){
l = new TLinkedList();
pushedBack.put(rObj, l);
}
m.tpack(new TInteger(type));
l.addLast(m);
logger.exiting("ConsensusExecution", "pushback");
}
private void sendPropose() {
GroupCommMessage proposeMessage = new GroupCommMessage();
//m = <<>>
proposeMessage.tpack(estimateFromC);
//m = <<estimate>>
proposeMessage.tpack(new TInteger(round));
//m = <<round::estimate>>
proposeMessage.tpack(new TInteger(CONS_PROPOSE));
//m = <<CONS_PROPOSE::round::estimate>>
proposeMessage.tpack(k);
//m = <<k::CONS_PROPOSE::round::estimate>>
triggerSend(proposeMessage, others);
}
private void sendSuspect() {
GroupCommMessage suspectMessage = new GroupCommMessage();
//m = <<>>
suspectMessage.tpack(new TInteger(-1));
//m = <<estimate>>
suspectMessage.tpack(new TInteger(round));
//m = <<round::estimate>>
suspectMessage.tpack(new TInteger(CONS_NACK));
//m = <<CONS_PROPOSE::round::estimate>>
suspectMessage.tpack(k);
//m = <<k::CONS_PROPOSE::round::estimate>>
triggerSend(suspectMessage, others);
}
private void broadcastDecision() {
GroupCommMessage decisionMessage = new GroupCommMessage();
//m = <<>>
decisionMessage.tpack(group);
//m = <<group>>
//decisionMessage.pack(hardClone(estimate));//Because I'm sending to myself
decisionMessage.tpack(estimate);
//m = <<decision::group>>
decisionMessage.tpack(new TInteger(CONS_RBCAST));
//m = <<CONS_BROADCAST::decision::group>>
decisionMessage.tpack(k);
//m = <<k::CONS_BROADCAST::decision::group>>
triggerSend(decisionMessage, group);
}
private void triggerSend(GroupCommMessage m, TList g) {
for (int i = 0; i < g.size(); i++) {
triggerSend(m.cloneGroupCommMessage(), (PID) g.get(i));
}
}
private void triggerSend(GroupCommMessage m, PID p) {
GroupCommEventArgs pt2ptSend = new GroupCommEventArgs();
pt2ptSend.addLast(m);
pt2ptSend.addLast(p);
pt2ptSend.addLast(new TBoolean(false)); // not promisc
logger.log(
Level.FINE,
"Sending Pt2Pt message {0} to {1}",
new Object[] { m, p });
trigger.trigger(Constants.PT2PTSEND, pt2ptSend);
}
public String toString() {
return new String(
"(** k: "
+ k
+ " r: "
+ round
+ " phase: "
+ phase
+ " coord: "
+ coordinator
+ " nbEstimate: "
+ numReceived
+ " estimate: "
+ estimate
+ " estimateFromC: "
+ estimateFromC
+ " suspected: "
+ suspected
// + " group: "
// + group
+ " pushedBack: "
+ pushedBack
+ "**)");
}
//TODO:remove!
public void marshal(MarshalStream arg0) throws IOException {
throw new IOException("unimplemented!");
}
public void unmarshalReferences(UnmarshalStream arg0) throws IOException, ClassNotFoundException {
throw new IOException("unimplemented!");
}
public Object deepClone(DeepClone arg0) throws CloneNotSupportedException {
throw new CloneNotSupportedException("unimplemented!");
}
}