/**
* 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.rpt2pt;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
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.FlowControl;
import framework.libraries.Serialize;
import framework.libraries.Timer;
import framework.libraries.Trigger;
import framework.libraries.serialization.TBoolean;
import framework.libraries.serialization.TByteArray;
import framework.libraries.serialization.TCollection;
import framework.libraries.serialization.THashMap;
import framework.libraries.serialization.THashSet;
import framework.libraries.serialization.TInteger;
import framework.libraries.serialization.TLinkedList;
import framework.libraries.serialization.TMap;
import framework.libraries.serialization.TSet;
import framework.libraries.tcp.Connection;
import framework.libraries.tcp.NonBlockingTCP;
import framework.libraries.tcp.Server;
/**
* <b> This class implements the event handlers for the Reliable Channel layer. </b>
* <hr>
* <b> Input Events: </b>
* <dl>
* <dt> <i>Init</i> </dt> <dd> Initializes the Reliable and unreliable layer. </dd>
* <dt> <i>Pt2PtSend</i> </dt> <dd> Sends a message to a process reliably. </dd>
* <dt> <i>Recv</i> </dt> <dd> Receives a message from a process unreliably. </dd>
* <dt> <i>JoinRemoveList</i> </dt> <dd> Processes to be added or to be removed
* into/from the known processes. </dd>
* <b> Output Events: </b>
* <dl>
* <dt> <i>Pt2PtDeliver</i> </dt> <dd> Delivers a message to the upper layer. </dd>
* <dt> <i>Send</i> </dt> <dd> Sends a message using the network transport
* </dl>
*/
public class ReliablePt2Pt {
//State Definitions for the state-machine
private static final int ST_NULL = 0;
private static final int ST_HIDDEN = 1;
private static final int ST_CONNECTING = 2;
private static final int ST_CONNECTED = 3;
private static final int ST_CLOSED = 4;
private static final int ST_CROSS = 5;
private static final int SUSPECT = 1;
private static final int RETRY = 2;
private static final int FC_THRESHOLD = 3000;
private static final int TO_SUSPECT = 60 * 1000; //Miliseconds
private static final int TO_RETRY_CONNECT = 5 * 1000; //Miliseconds
private static final int MAX_RETRIES = 200;
private static final int PROMISC = 1;
private static final int NORMAL = 2;
private static final int ACK = 3;
private static final int CHOSEN = 4;
private int fc_threshold;
private int smoothness = 1000;
private int to_suspect;
private int to_retry_connect;
private PID myself;
private FlowControl flow_control = null;
private int fc_key;
private Trigger trigger = null;
private Timer timer = null;
private NonBlockingTCP tcp = null;
private Serialize serialize = null;
private TMap connections = null;
private Server server = null;
private boolean blocked = false;
private static final Logger logger =
Logger.getLogger(ReliablePt2Pt.class.getName());
public static class ConnectionData implements Transportable{
//TODO: remove implements Transportable
protected Connection connection;
protected Connection connection2;
//TODO: what would happen if these guys were SETS (no order)
protected TLinkedList bufferOut; //Can't be TLinkedList
protected TLinkedList bufferIn;
protected int state;
protected boolean full;
protected boolean sendingThreadReady;
protected int retries;
protected int joins;
protected ConnectionData(int state) {
connection = null;
connection2 = null;
bufferOut = new TLinkedList();
bufferIn = new TLinkedList();
this.state = state;
full = false;
sendingThreadReady = false;
retries = 0;
joins = 1;
}
public String toString() {
String st = "ST_NULL";
switch (this.state) {
case 1 :
st = "ST_HIDDEN";
break;
case 2 :
st = "ST_CONNECTING";
break;
case 3 :
st = "ST_CONNECTED";
break;
case 4 :
st = "ST_CLOSED";
break;
case 5 :
st = "ST_CROSS";
}
return new String(
"ConnectionData: "
+ "\n\tConnection: "
+ connection
+ "\n\tConnection2: "
+ connection2
+ "\n\tBufferOut (size): "
+ bufferOut.size()
+ "\n\tBufferIn: "
+ bufferIn
+ "\n\tState: "
+ st
+ "\n\tFull: "
+ full
+ "\n\tSendingThreadReady: "
+ sendingThreadReady
+ "\n\tJoins: "
+ joins);
}
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!");
}
}
/**
* Constructor.
*
*/
public ReliablePt2Pt(
Trigger trigger,
FlowControl fc,
NonBlockingTCP tcp,
Serialize serialize,
Timer timer,
PID myself) {
logger.entering("ReliablePt2Pt", "<constr>");
this.trigger = trigger;
this.flow_control = fc;
this.tcp = tcp;
this.serialize = serialize;
this.timer = timer;
this.myself = myself;
connections = new THashMap();
logger.exiting("ReliablePt2Pt", "<constr>");
}
public void handleInit(GroupCommEventArgs arg) throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleInit");
int fc_threshold = ((TInteger) arg.removeFirst()).intValue();
int to_suspect = ((TInteger) arg.removeFirst()).intValue();
int to_retry_connect = ((TInteger) arg.removeFirst()).intValue();
if (fc_threshold == 0)
fc_threshold = FC_THRESHOLD;
if (to_suspect == 0)
to_suspect = TO_SUSPECT;
if (to_retry_connect == 0)
to_retry_connect = TO_RETRY_CONNECT;
fc_key = flow_control.getFreshKey();
//flow_control.setThreshold(fc_key, 1);
this.fc_threshold = fc_threshold;
GroupCommMessage suspect = new GroupCommMessage();
suspect.tpack(new TInteger(SUSPECT));
timer.schedule(suspect, true, to_suspect);
this.to_suspect = to_suspect;
this.to_retry_connect = to_retry_connect;
this.blocked = false;
server = tcp.startServer(myself);
logger.exiting("ReliablePt2Pt", "handleInit");
}
public void handleJoinRemoveList(GroupCommEventArgs arg)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleJoinRemoveList");
TSet newProcesses = (TSet) arg.removeFirst();
TSet removeProcesses = (TSet) arg.removeFirst();
// Join
Iterator i = newProcesses.iterator();
while (i.hasNext()) {
PID pid = (PID) i.next();
doJoin(pid);
}
// Remove
i = removeProcesses.iterator();
while (i.hasNext()) {
PID pid = (PID) i.next();
doRemove(pid);
}
logger.exiting("ReliablePt2Pt", "handleJoinRemoveList");
}
/**
* Add a new process to already known processes. </br>
*
* @param sp The set of processes to add.
*/
private void doJoin(PID pid) throws GroupCommException {
logger.entering("ReliablePt2Pt", "doJoin");
// if (pid.equals(myself)) {
// selfConnected++;
// logger.exiting("ReliablePt2Pt", "doJoin");
// return;
// }
switch (getState(pid)) {
case ST_NULL :
//Creates a new connection
tcp.connect(pid);
setState(pid, ST_CONNECTING);
//This resets the nomber of retries
logger.log(
Level.FINE,
"Connection to {0} created. State ST_CONNECTING",
pid);
break;
case ST_HIDDEN :
//Reusing the connection that pid already established
setState(pid, ST_CONNECTED);
logger.log(
Level.FINE,
"Hidden connection to {0} reused. State ST_CONNECTED",
pid);
tcp.startSender(getConnection(pid));
tcp.startReceiver(getConnection(pid));
flushInputBuffer(pid);
break;
case ST_CONNECTING :
case ST_CONNECTED :
case ST_CLOSED :
case ST_CROSS :
logger.log(
Level.FINE,
"Connecting more than once to {0}.",
pid);
incJoins(pid);
break;
default :
throw new GroupCommException(
"ReliablePt2Pt:doJoin:"
+ "Hmmm, weird. State of pid unknown");
}
logger.exiting("ReliablePt2Pt", "doJoin");
}
/**
* Remove a set of processes from already known processes.
*
* @param lp The set of processes to remove.
*/
private void doRemove(PID pid) throws GroupCommException {
logger.entering("ReliablePt2Pt", "doRemove");
// if (pid.equals(myself)) {
// if (--selfConnected < 0)
// throw new GroupCommException(
// "Trying to self-disconnect while not self-connected.");
// logger.exiting("ReliablePt2Pt", "doRemove");
// return;
// }
switch (getState(pid)) {
case ST_NULL :
throw new GroupCommException(
"Disconnnecting from unknown process " + pid);
case ST_HIDDEN :
throw new GroupCommException("Connection to pid doesn't exist (hidden)");
case ST_CROSS :
case ST_CONNECTED :
case ST_CONNECTING :
if (!decJoins(pid)) {
if (getState(pid) != ST_CONNECTING)
tcp.disconnect(getConnection(pid));
removeState(pid); //We forget the process
logger.log(
Level.FINE,
"Disconnecting from {0}. Forgetting process",
pid);
//Check if we can release the application
// (Application is blocked and no queue is full)
if (blocked && !fullQueues()) {
//Release application
flow_control.release(fc_key);
logger.fine("Releasing application's flow control");
blocked = false;
}
}
break;
case ST_CLOSED :
if (!decJoins(pid)) {
//Connection ceased to exist: no need to close
removeState(pid); //We forget the process
logger.log(
Level.FINE,
"Already disconneced from {0}. Just forgetting it",
pid);
}
break;
default :
throw new GroupCommException(
"ReliablePt2Pt:doRemove:"
+ "Hmmm, weird. State of pid unknown");
}
logger.exiting("ReliablePt2Pt", "doRemove");
}
public void handleConnected(GroupCommEventArgs arg)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleConnected");
PID pid = (PID) arg.removeFirst();
Connection c = (Connection) arg.removeFirst();
if (c == null) {
if (getState(pid) == ST_NULL) {
logger.log(
Level.INFO,
"Disconneced from {0} when handling connected. Weird",
pid);
return;
}
ConnectionData cd = (ConnectionData) connections.get(pid);
cd.retries++;
GroupCommMessage retry = new GroupCommMessage();
//msg = RETRY::pid
retry.tpack(pid);
retry.tpack(new TInteger(RETRY));
timer.schedule(retry, false, to_retry_connect);
logger.log(
Level.FINE,
"Connection establishment to {0} failed. Retrying...",
pid);
logger.exiting("ReliablePt2Pt", "handleConnected");
return;
}
switch (getState(pid)) {
case ST_NULL :
//Maybe I Connected and Disconnected too swiftly
//We disconnect the socket
tcp.disconnect(c);
logger.log(
Level.INFO,
"Received connected from NULL-state connection to {0}. Disconnecting",
pid);
break;
case ST_HIDDEN :
throw new GroupCommException(
"ReliablePt2Pt:handleConnected:"
+ "Two incomming (hidden) connections for the same pid??");
case ST_CONNECTING :
if (myself.equals(pid)) {
//I am self-connecting
setState(pid, ST_CONNECTED);
mapConnection(pid, c);
tcp.startSender(c);
}
case ST_CROSS :
//Ok, now we need to wait for the ACK message from the other endpoint
tcp.startReceiver(c);
break;
case ST_CONNECTED :
throw new GroupCommException(
"ReliablePt2Pt:handleConnected:"
+ "Two connections for the same pid??");
case ST_CLOSED :
//Maybe I Connected and Disconnected too swiftly
// and I was in a crossed connection
//We ignore it
tcp.disconnect(c);
logger.log(
Level.INFO,
"Received connected from CLOSED-state connection to {0}. Disconnecting",
pid);
break;
default :
throw new GroupCommException(
"ReliablePt2Pt:handleConnected:"
+ "Hmmm, weird. State of pid unknown");
}
logger.exiting("ReliablePt2Pt", "handleConnected");
}
public void handleClosed(GroupCommEventArgs arg)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleClosed");
Connection c = (Connection) arg.removeFirst();
PID pid = tcp.getRemotePID(c);
//Shutdown the sending stream
tcp.disconnect(c);
closed_broken(pid, c);
logger.exiting("ReliablePt2Pt", "handleClosed");
}
public void handleBroken(GroupCommEventArgs arg)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleBroken");
Connection c = (Connection) arg.removeFirst();
PID pid = tcp.getRemotePID(c);
//Same treatment as HandleClosed
closed_broken(pid, c);
logger.exiting("ReliablePt2Pt", "handleBroken");
}
private void closed_broken(PID pid, Connection c)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "closed_broken");
switch (getState(pid)) {
case ST_NULL :
//Crossed closing
//We ignore it
logger.log(Level.INFO, "Ignoring crossed closing to: {0}", pid);
break;
case ST_HIDDEN :
//Some process contacted me and then disconnected. But I don't know it
removeState(pid); //We forget the process
logger.log(Level.FINE, "Hidden connection closed by: {0}", pid);
break;
case ST_CONNECTED :
ConnectionData cd = (ConnectionData) connections.get(pid);
if (c != cd.connection) {
//Closing not-chosen connection
logger.log(
Level.FINE,
"Closing not-chosen connection to: {0}",
pid);
break;
}
case ST_CROSS :
//The remote process has closed the connection
setState(pid, ST_CLOSED);
unmapConnection(pid);
logger.log(Level.FINE, "Connection closed by: {0}", pid);
((ConnectionData) connections.get(pid)).bufferOut.clear();
//Check if we can release the application
// (Application is blocked and no queue is full)
if (blocked && !fullQueues()) {
//Release application
flow_control.release(fc_key);
logger.fine("Releasing application's flow control");
blocked = false;
}
break;
case ST_CONNECTING :
//Maybe incarnations didn't match
logger.log(
Level.FINE,
"Connection closed by: {0} (while in ST_CONNECTING)",
pid);
setState(pid, ST_CLOSED);
((ConnectionData) connections.get(pid)).bufferOut.clear();
//Check if we can release the application
// (Application is blocked and no queue is full)
if (blocked && !fullQueues()) {
//Release application
flow_control.release(fc_key);
logger.fine("Releasing application's flow control");
blocked = false;
}
break;
case ST_CLOSED :
//Maybe both the reader and the sender threads are reporting
// the same connection broken. Ignore it
logger.log(
Level.FINE,
"The remote pid {0} can't close a connection twice",
pid);
break;
default :
throw new GroupCommException(
"ReliablePt2Pt:handleClosed:"
+ "Hmmm, weird. State of pid unknown");
}
logger.exiting("ReliablePt2Pt", "closed_broken");
}
public void handleAccepted(GroupCommEventArgs arg)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleAccept");
Connection c = (Connection) arg.removeFirst();
PID pid = tcp.getRemotePID(c);
switch (getState(pid)) {
case ST_NULL :
//A new process has contacted me
logger.log(Level.FINE, "Hiding connection from: {0}", pid);
setState(pid, ST_HIDDEN);
mapConnection(pid, c);
tcp.startReceiver(c);
//Sending ACK message with value TRUE
GroupCommMessage ackMsg = new GroupCommMessage();
//ackMsg = ACK::true
ackMsg.tpack(new TBoolean(true));
ackMsg.tpack(new TInteger(ACK));
tcp.sendMessage(serialize.marshall(ackMsg), c);
break;
case ST_HIDDEN :
throw new GroupCommException(
"ReliablePt2Pt:handleAccept:"
+ "Two incomming (hidden) connections for the same pid??");
case ST_CONNECTING :
//Oh no, crossed connection!! We will receive an "open" later on
logger.log(
Level.FINE,
"Crossed connection to: {0}. Waiting for 'connected' event",
pid);
setState(pid, ST_CROSS);
mapConnection(pid, c);
//Sending ACK message with value FALSE... for the other
//process to learn about the crossed connection
ackMsg = new GroupCommMessage();
//ackMsg = ACK::false
ackMsg.tpack(new TBoolean(false));
ackMsg.tpack(new TInteger(ACK));
tcp.sendMessage(serialize.marshall(ackMsg), c);
break;
case ST_CONNECTED :
throw new GroupCommException(
"ReliablePt2Pt:handleAccept:"
+ "Two incomming connections for the same pid??");
case ST_CLOSED :
throw new GroupCommException(
"ReliablePt2Pt:handleAccept:"
+ "The remote pid can't have closed a not yet created connection");
case ST_CROSS :
//Sending ACK message with value FALSE... for the other
//process to learn about the crossed connection
ackMsg = new GroupCommMessage();
ackMsg.tpack(new TBoolean(false));
ackMsg.tpack(new TInteger(ACK));
//ackMsg = ACK::false
tcp.sendMessage(serialize.marshall(ackMsg), c);
Connection c2 = getConnection(pid);
//unmapConnection(pid); not necessary
//Compare the two connections
logger.log(
Level.FINE,
"Comparing crossed connections to {0}....",
pid);
if (c.compareTo(c2) < 0) {
Connection caux = c2;
c2 = c;
c = caux;
}
//From here on, c2 has the smallest ID
//So, I keep c
logger.log(Level.FINE, "Connection chosen: {0}", c);
setState(pid, ST_CONNECTED);
mapConnection(pid, c);
ConnectionData cd = (ConnectionData) connections.get(pid);
cd.connection2 = c2;
//Sending CHOSEN message to be able to
//cleanly close the connection that wasn't chosen
GroupCommMessage chosenMsg = new GroupCommMessage();
chosenMsg.tpack(new TInteger(CHOSEN));
//chosenMsg = CHOSEN
tcp.sendMessage(serialize.marshall(chosenMsg), c);
//The connection whose Accept I handled, hasn't yet started the receiver...
//... so it may be the chosen one, it may not
tcp.startReceiver(c);
break;
default :
throw new GroupCommException(
"ReliablePt2Pt:handleAccept:"
+ "Hmmm, weird. State of pid unknown");
}
logger.exiting("ReliablePt2Pt", "handleAccept");
}
/**
* The handler for the <i>Pt2PtSend</i> event. </br>
* It sends a message to a process.
*
* @param l <dl>
* <dt> arg1 : GroupCommMessage </dt> <dd> Le message. </dd>
* <dt> arg2 : PID </dt> <dd> La destination du message. </dd>
* <dt> arg3 : Boolean </dt> <dd> Attribut utilis� par la destination </dd>
* </dl>
*/
public void handlePt2PtSend(GroupCommEventArgs arg)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "handlePt2PtSend");
GroupCommMessage m = (GroupCommMessage) arg.removeFirst();
PID pid = (PID) arg.removeFirst();
boolean promisc = ((TBoolean) arg.removeFirst()).booleanValue();
if (m == null)
throw new GroupCommException(
"ReliablePt2Pt:handlePt2PtSend:" + "Message can't be null");
// if (pid.equals(myself)) {
// //In this case, "promisc" is completely useless
// if (selfConnected < 1)
// throw new GroupCommException(
// "ReliablePt2Pt:handlePt2PtSend:"
// + "Self-connection doesn't exist");
// //Even if the message is bound to myself, I serialize it.
// // This way I avoid surprises with later side-effects
// GroupCommMessage m2 = tcp.unmarshallMessage(tcp.marshallMessage(m));
// GroupCommEventArgs args = new GroupCommEventArgs();
// args.addLast(m2);
// args.addLast(pid);
// trigger.trigger(Constants.PT2PTDELIVER, args);
// //YOU CANNOT DO THIS, IT BREAKS ATOMICITY
// logger.exiting("ReliablePt2Pt", "handlePt2PtSend");
// return;
// }
if (promisc) {
//m = PROMISC::<payload>
m.tpack(new TInteger(PROMISC));
} else {
//m = NORMAL::<payload>
m.tpack(new TInteger(NORMAL));
}
switch (getState(pid)) {
case ST_NULL :
throw new GroupCommException(
"ReliablePt2Pt:handlePt2PtSend:"
+ "Connection to pid "
+ pid
+ " doesn't exist");
case ST_HIDDEN :
throw new GroupCommException(
"ReliablePt2Pt:handlePt2PtSend:"
+ "Connection to pid "
+ pid
+ " doesn't exist (hidden)");
case ST_CONNECTING :
case ST_CROSS :
case ST_CONNECTED :
logger.log(
Level.FINE,
"Sending message to {0}:\n\tMessage:{1}",
new Object[] { pid, m });
byte[] b = serialize.marshall(m);
ConnectionData cd = (ConnectionData) connections.get(pid);
if (cd.sendingThreadReady) {
// The sending thread is blocked on the output buffer, since it's empty
// ... thus, the message should be sent immediately
tcp.setMessageToSend(b, getConnection(pid));
cd.sendingThreadReady = false;
cd.full = false;
} else {
// The output buffer is not empty
// ... thus, queue the message for deferred sending
cd.bufferOut.addLast(new TByteArray(b));
if (!blocked && cd.bufferOut.size() > fc_threshold) {
//Block application
logger.fine("Blocking application's flow control");
flow_control.block(fc_key);
blocked = true;
}
}
break;
case ST_CLOSED :
//Message discarded
logger.log(
Level.FINE,
"Discarding message to {0}:\n\tMessage:{1}",
new Object[] { pid, m });
break;
default :
throw new GroupCommException(
"ReliablePt2Pt:handlePt2PtSend:"
+ "Hmmm, weird. State of pid unknown");
}
logger.exiting("ReliablePt2Pt", "handlePt2PtSend");
}
public void handleReadyForNextMessage(GroupCommEventArgs arg) {
logger.entering("ReliablePt2Pt", "handleReadyForNextMessage");
Connection c = (Connection) arg.removeFirst();
PID pid = tcp.getRemotePID(c);
if (getState(pid) != ST_CONNECTED) {
logger.log(
Level.INFO,
"Discarding readyForNextMessage from {0}",
pid);
logger.exiting("ReliablePt2Pt", "handleReadyForNextMessage");
return;
}
ConnectionData cd = (ConnectionData) connections.get(pid);
if (cd.bufferOut.size() > 0) {
logger.fine("Sending first message in the output buffer");
byte[] b = ((TByteArray)cd.bufferOut.removeFirst()).byteValue();
tcp.setMessageToSend(b, c);
cd.sendingThreadReady = false;
if (blocked && !fullQueues()) {
logger.fine("Releasing application's flow control");
flow_control.release(fc_key);
blocked = false;
}
} else {
logger.fine("Output buffer is empty: no message has to be sent");
cd.sendingThreadReady = true;
}
cd.full = false;
logger.exiting("ReliablePt2Pt", "handleReadyForNextMessage");
}
/**
* The handler for the <i>Recv</i> event. </br>
* It occured when a message is received from a process. </br>
* <hr>
* If the process is known, the message is send to the upper layer. </br>
* If the process is unknown, but the special attribute promisc is set,
* then the process is added to the known processes and the message
* is delivered. </br>
* If the process is unknown, and the special attribute promisc is unset,
* then the couple (process, message) is added to the Masked Processes. </br>
*
* @param l <dl>
* <dt> arg1 : GroupCommMessage </dt> <dd> Le message. </dd>
* <dt> arg2 : PID </dt> <dd> La source du message. </dd>
* </dl>
*
* @see KnownProcesses The set of Known Processes.
* @see MaskedProcesses The set of Masked Processes.
* @see Reliable_Pt2Pt The class which contains the method to send a message to the
* upper layer.
*/
public void handleRecv(GroupCommEventArgs arg) throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleRecv");
byte[] b = ((TByteArray) arg.removeFirst()).byteValue();
Connection c = (Connection) arg.removeFirst();
PID pid = tcp.getRemotePID(c);
GroupCommMessage m = null;
try {
m = (GroupCommMessage) serialize.unmarshall(b);
} catch (Exception e) {
if (getState(pid) == ST_CONNECTED) {
logger.log(
Level.INFO,
"Corrupted TCP stream from {0} while connected. Closing connection...",
pid);
//Shutdown the sending stream
tcp.disconnect(c);
//Acxt as though the remote process has closed the connection
closed_broken(pid, c);
//Suspect it at once
GroupCommMessage suspect = new GroupCommMessage();
suspect.tpack(new TInteger(SUSPECT));
timer.schedule(suspect, false, 10);
return;
} else {
logger.log(
Level.INFO,
"Corrupted TCP stream from {0}. Exiting...",
pid);
System.exit(1);
}
}
//m = type::<payload>
int type = ((TInteger) m.tunpack()).intValue();
//m = <payload>
switch (getState(pid)) {
case ST_NULL :
//Maybe I Connected and Disconnected too swiftly. Very weird!!
//Disconnect from it
logger.log(
Level.INFO,
"Receiving data from unknown connection to {0}. WEIRD!! Disconnecting",
pid);
tcp.disconnect(c);
break;
case ST_CLOSED :
// throw new GroupCommException(
// "ReliablePt2Pt: handleRecv:"
// + "The remote process can't send messages on a closed connection");
//It might happen that the sending thread gets a "broken" event, more or less at the
// same time that the receiving thread receives a message.
//If sending thread overtakes receiving thread, we might receive a message
// on a CLOSED connection
// SO WE DISCARD IT...
logger.log(
Level.INFO,
"Receiving data from closed connection to {0}. WEIRD!! Disconnecting",
pid);
tcp.disconnect(c);
break;
case ST_CONNECTING :
//I shouldn't be able to receive normal messages yet
if (type != ACK)
throw new GroupCommException(
"ReliablePt2Pt: handleRecv:"
+ "The remote process can't send messages on a not yet created connection");
//We may have a crossed connetion. Let's check it
if (((TBoolean) m.tunpack()).booleanValue()) {
//All right, just one connection
logger.log(
Level.FINE,
"Connection to {0} established",
pid);
setState(pid, ST_CONNECTED);
mapConnection(pid, c);
tcp.startSender(c);
} else {
//Oh no, a crossed connection, let's wait for the accept
logger.log(
Level.FINE,
"Crossed connection to {0} waiting for Accept",
pid);
setState(pid, ST_CROSS);
mapConnection(pid, c);
}
break;
case ST_CROSS :
if (type == ACK) {
if (((TBoolean) m.tunpack()).booleanValue())
throw new GroupCommException(
"ReliablePt2Pt: handleRecv:"
+ "If I am in CROSS state, the ACK I receive can't be TRUE!!");
Connection c2 = getConnection(pid);
//unmapConnection(pid); not necessary
//Compare the two connections
logger.log(
Level.FINE,
"Comparing crossed connections to {0}....",
pid);
if (c.compareTo(c2) < 0) {
Connection caux = c2;
c2 = c;
c = caux;
}
//From here on, c2 has the smallest ID
//So, I keep c
logger.log(Level.FINE, "Connection chosen: {0}", c);
setState(pid, ST_CONNECTED);
mapConnection(pid, c);
ConnectionData cd = (ConnectionData) connections.get(pid);
cd.connection2 = c2;
//Sending CHOSEN message to be able to
//cleanly close the connection that wasn't chosen
GroupCommMessage chosenMsg = new GroupCommMessage();
chosenMsg.tpack(new TInteger(CHOSEN));
//chosenMsg = CHOSEN
tcp.sendMessage(serialize.marshall(chosenMsg), c);
//The connection whose Accept I handled, hasn't yet started the receiver...
//... so it may be the chosen one, it may not
tcp.startReceiver(c);
} else if (type == CHOSEN) {
throw new GroupCommException(
"Received CHOSEN messages while in ST_CROSS."
+ "Impossible: receiver thread not started");
} else {
throw new GroupCommException("Received normal messages while in ST_CROSS!!");
}
break;
case ST_CONNECTED :
//Message from connected process pid
if (type == ACK)
throw new GroupCommException(
"ReliablePt2Pt: handleRecv: If I am "
+ "in CONNECTED state, I can't receive ACK messages");
ConnectionData cd = (ConnectionData) connections.get(pid);
if (getConnection(pid) != c)
throw new GroupCommException(
"ReliablePt2Pt: handleRecv: "
+ "Received a message from an unmapped connection");
if (type == CHOSEN) {
if (cd.connection2 == null)
throw new GroupCommException(
"ReliablePt2Pt: handleRecv: "
+ "Connection2 can't be null when receiving CHOSEN message");
tcp.startSender(cd.connection);
tcp.disconnect(cd.connection2);
cd.connection2 = null;
} else { //Normal message TODO: add assertion!
if (cd.connection2 != null)
throw new GroupCommException(
"ReliablePt2Pt: handleRecv: "
+ "Connection2 must be null when receiving a normal message");
logger.log(
Level.FINE,
"Data from: {0}\n\t Message: {1}",
new Object[] { pid, m });
GroupCommEventArgs args = new GroupCommEventArgs();
args.addLast(m);
args.addLast(pid);
trigger.trigger(Constants.PT2PTDELIVER, args);
}
break;
case ST_HIDDEN :
if (type == ACK)
throw new GroupCommException(
"ReliablePt2Pt: handleRecv: If I am "
+ "in HIDDEN state, I can't receive ACK messages");
if (type == CHOSEN)
throw new GroupCommException(
"ReliablePt2Pt: handleRecv: If I am "
+ "in HIDDEN state, I can't receive CHOSEN messages");
tcp.stopReceiver(c);
cd = (ConnectionData) connections.get(pid);
if (type == NORMAL) { //Not promiscuous
logger.log(
Level.FINE,
"Buffering data from: {0} (hidden)\n\t Message: {1}",
new Object[] { pid, m });
//It is a queue because of asynchrony of recv and stopReceiver
cd.bufferIn.addLast(m);
} else { //Promiscuous
//Yes, we change order because this has to be the first message to be delivered
logger.log(
Level.FINE,
"Promicuous message from: {0} (hidden)\n\t Message: {1}",
new Object[] { pid, m });
cd.bufferIn.addFirst(m);
doJoin(pid);
//Because this join is not a normal one, we shouldn't count it
decJoins(pid);
}
break;
default :
throw new GroupCommException(
"ReliablePt2Pt: handleRecv:"
+ "Hmmm, weird. State of pid unknown");
}
logger.exiting("ReliablePt2Pt", "handleRecv");
}
public void handleTimeout(GroupCommEventArgs arg)
throws GroupCommException {
logger.entering("ReliablePt2Pt", "handleTimeout");
GroupCommMessage mclone = (GroupCommMessage) arg.removeFirst();
GroupCommMessage m = mclone.cloneGroupCommMessage();
//m = type::<payload>
int type = ((TInteger) m.tunpack()).intValue();
//m = <payload>
switch (type) {
case RETRY :
PID pid = (PID) m.tunpack();
ConnectionData cd = (ConnectionData) connections.get(pid);
switch (getState(pid)) {
case ST_NULL :
//I quickly closed the connection
case ST_CLOSED :
//The remote process quickly closed the connection
break;
case ST_CONNECTING :
if (cd.retries > MAX_RETRIES) {
//We give up
logger.log(
Level.FINE,
"Giving up connection to {0}",
pid);
setState(pid, ST_CLOSED);
cd.bufferOut.clear();
//Check if we can release the application
// (Application is blocked and no queue is full)
if (blocked && !fullQueues()) {
//Release application
flow_control.release(fc_key);
logger.fine(
"Releasing application's flow control");
blocked = false;
}
} else {
logger.log(
Level.FINE,
"Retrying connection to {0}",
pid);
tcp.connect(pid);
}
break;
case ST_CROSS :
if (cd.retries > MAX_RETRIES) {
//We give up
logger.log(
Level.FINE,
"Couldn't set up connection to {0}. Giving up",
pid);
setState(pid, ST_CLOSED);
unmapConnection(pid);
cd.bufferOut.clear();
//Check if we can release the application
// (Application is blocked and no queue is full)
if (blocked && !fullQueues()) {
//Release application
flow_control.release(fc_key);
logger.fine(
"Releasing application's flow control");
blocked = false;
}
} else {
logger.log(
Level.FINE,
"Retrying connection to {0}",
pid);
tcp.connect(pid);
}
break;
case ST_HIDDEN :
case ST_CONNECTED :
default :
throw new GroupCommException(
"ReliablePt2Pt: handleTimeout: If connection didn't establish,"
+ " state must be ST_CONNECTING or ST_CROSS");
}
break;
case SUSPECT :
TSet suspected = new THashSet();
//Check all queues
TCollection keys = connections.keySet();
Iterator i = keys.iterator();
while (i.hasNext()) {
boolean suspect = false;
pid = (PID) i.next();
switch (getState(pid)) {
case ST_CONNECTED :
case ST_CONNECTING :
case ST_CROSS :
cd = (ConnectionData) connections.get(pid);
if (!cd.sendingThreadReady) {
//The queue is not empty
suspect = cd.full;
cd.full = true;
}
break;
case ST_CLOSED :
suspect = true;
case ST_HIDDEN :
//Do nothing, we don't (yet) have an outgoing queue
}
if (suspect) {
logger.log(
Level.FINE,
"Sending event Suspect2 for process: {0}",
pid);
suspected.add(pid);
}
}
if (suspected.size() > 0) {
//Trigger Suspect Event
GroupCommEventArgs args = new GroupCommEventArgs();
args.addLast(suspected);
trigger.trigger(Constants.SUSPECT2, args);
}
}
logger.exiting("ReliablePt2Pt", "handleTimeout");
}
/**
*
*/
public void handleShutdown() throws GroupCommException {
//Close server
tcp.stopServer(server);
//Close all open connections
TCollection keys = connections.keySet();
Iterator i = keys.iterator();
logger.fine("Shutting down.");
while (i.hasNext()) {
PID p = (PID) i.next();
if (getState(p) != ST_CLOSED) {
Connection c = getConnection(p);
if (c != null) {
logger.log(Level.FINE, "Disconnecting from {0}", p);
tcp.disconnect(c);
}
}
}
// Clear map
connections.clear();
}
private void setState(PID p, int state) {
logger.log(
Level.FINE,
"Setting state of {0} to {1}",
new Object[] { p, new Integer(state)});
if (state == ST_NULL) {
connections.remove(p);
} else {
if (!connections.containsKey(p)) {
ConnectionData c = new ConnectionData(state);
connections.put(p, c);
} else {
((ConnectionData) connections.get(p)).state = state;
}
}
}
private int getState(PID p) {
if (!connections.containsKey(p)) {
logger.log(Level.FINE, "Getting state of {0}: ST_NULL", p);
return ST_NULL;
} else {
int st = ((ConnectionData) connections.get(p)).state;
logger.log(
Level.FINE,
"Getting state of {0}: {1}",
new Object[] { p, new Integer(st)});
return st;
}
}
private void removeState(PID p) {
setState(p, ST_NULL);
}
private void mapConnection(PID p, Connection connection)
throws GroupCommException {
if (!connections.containsKey(p))
throw new GroupCommException(
"ReliablePt2Pt: mapConnection:" + "PID unkonwn");
ConnectionData c = (ConnectionData) connections.get(p);
if (c.state == ST_CLOSED || c.state == ST_CONNECTING)
throw new GroupCommException(
"ReliablePt2Pt: mapConnection:"
+ "Bad state, no connection mapped");
logger.log(
Level.FINE,
"Mapping connection {0} to {1}",
new Object[] { connection, p });
c.connection = connection;
}
private void unmapConnection(PID p) throws GroupCommException {
if (!connections.containsKey(p))
throw new GroupCommException(
"ReliablePt2Pt: unmapConnection:" + "PID unkonwn");
ConnectionData c = (ConnectionData) connections.get(p);
if (c.state == ST_CONNECTED
|| c.state == ST_HIDDEN
|| c.state == ST_CROSS)
throw new GroupCommException(
"ReliablePt2Pt: unmapConnection:"
+ "Bad state, connection can't be unmapped");
logger.log(
Level.FINE,
"Unmapping connection {0} to {1}",
new Object[] { c.connection, p });
c.connection = null;
}
private Connection getConnection(PID p) throws GroupCommException {
if (!connections.containsKey(p))
throw new GroupCommException(
"ReliablePt2Pt: getConnection:" + "PID unkonwn");
ConnectionData c = (ConnectionData) connections.get(p);
if (c.state == ST_CLOSED)
throw new GroupCommException(
"ReliablePt2Pt: getConnection:"
+ "Bad state, no connection mapped");
//It may return null, if no connection was mapped
logger.log(
Level.FINE,
"Getting mapped connection {0} to {1}",
new Object[] { c.connection, p });
return c.connection;
}
private void incJoins(PID p) throws GroupCommException {
logger.log(Level.FINE, "Incrementing joins of {0}", p);
if (!connections.containsKey(p))
throw new GroupCommException(
"No state for " + p + "when trying to increment joins");
((ConnectionData) connections.get(p)).joins++;
}
/*
* Returns true if there are remaining joins to remove
*/
private boolean decJoins(PID p) throws GroupCommException {
logger.log(Level.FINE, "Decrementing joins of {0}", p);
if (!connections.containsKey(p))
throw new GroupCommException(
"No state for " + p + "when trying to decrement joins");
ConnectionData cd = (ConnectionData) connections.get(p);
return (--cd.joins > 0);
}
private boolean fullQueues() {
//Check all queues to know whether they're full
TCollection keys = connections.keySet();
Iterator i = keys.iterator();
boolean full = false;
while (i.hasNext()) {
PID p = (PID) i.next();
if (getState(p) == ST_CONNECTED
|| getState(p) == ST_CONNECTING
|| getState(p) == ST_CROSS) {
ConnectionData c = (ConnectionData) connections.get(p);
full =
full
|| (c.bufferOut.size()
> (fc_threshold - (fc_threshold / smoothness)));
}
}
logger.log(
Level.FINE,
"Checking queues... result {0}",
new Boolean(full));
return full;
}
private void flushInputBuffer(PID p) {
logger.entering("ReliablePt2Pt", "flushInputBuffer");
ConnectionData cd = (ConnectionData) connections.get(p);
//LinkedList lm = (LinkedList)cd.bufferIn.clone(); why clone????
TLinkedList lm = cd.bufferIn;
logger.log(
Level.FINE,
"Flushing messages from {0}: \n\t{1}",
new Object[] { p, lm });
while (lm.size() > 0) {
GroupCommEventArgs args = new GroupCommEventArgs();
args.addLast(lm.removeFirst());
args.addLast(p);
trigger.trigger(Constants.PT2PTDELIVER, args);
}
logger.exiting("ReliablePt2Pt", "flushInputBuffer");
}
/**
* Used for debugging. </br>
* Undocumented.
*/
public void handleDump(OutputStream out) {
PrintStream err = new PrintStream(out);
err.println("======== ReliablePt2Pt: dump =======");
err.println("fc_threshold: " + fc_threshold);
err.println("to_suspect: " + to_suspect);
err.println("to_retry_connect: " + to_retry_connect);
//err.println("self_connected: " + selfConnected);
err.println("Flow control is blocked: " + blocked);
err.println("Connections: ");
Iterator it = connections.keySet().iterator();
while (it.hasNext()) {
PID pid = (PID) it.next();
err.println(" PID:" + pid + "-->" + connections.get(pid));
err.println("----");
}
err.println("================================");
}
}