package ibis.ipl.impl.stacking.lrmc;
import ibis.ipl.IbisIdentifier;
import ibis.ipl.MessageUpcall;
import ibis.ipl.PortType;
import ibis.ipl.ReadMessage;
import ibis.ipl.ReceivePort;
import ibis.ipl.SendPort;
import ibis.ipl.WriteMessage;
import ibis.ipl.impl.stacking.lrmc.io.MessageReceiver;
import ibis.ipl.impl.stacking.lrmc.util.DynamicObjectArray;
import ibis.ipl.impl.stacking.lrmc.util.IbisSorter;
import ibis.ipl.impl.stacking.lrmc.util.Message;
import ibis.ipl.impl.stacking.lrmc.util.MessageCache;
import ibis.ipl.impl.stacking.lrmc.util.MessageQueue;
import ibis.util.TypedProperties;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LabelRoutingMulticast extends Thread implements MessageUpcall {
private final static int ZOMBIE_THRESHOLD = 100000;
private static final Logger logger = LoggerFactory
.getLogger(LabelRoutingMulticast.class);
final LrmcIbis ibis;
private final String name;
ReceivePort receive;
private MessageReceiver receiver;
private final MessageCache cache;
private final DynamicObjectArray<SendPort> sendports = new DynamicObjectArray<SendPort>();
private final DynamicObjectArray<Long> diedmachines = new DynamicObjectArray<Long>();
private boolean finish = false;
private int[] destinations = null;
private long bytes = 0;
private MessageQueue sendQueue;
public LabelRoutingMulticast(LrmcIbis ibis, MessageReceiver m,
MessageCache c, String name) throws IOException {
this.ibis = ibis;
this.receiver = m;
this.name = name;
this.cache = c;
this.sendQueue = new MessageQueue(
new TypedProperties(ibis.properties()).getIntProperty(
"lrmc.queueSize", 256));
receive = ibis.base.createReceivePort(LrmcIbis.additionalPortType, "LRMCRing-"
+ name, this);
receive.enableConnections();
receive.enableMessageUpcalls();
super.setName("LabelRoutingMulticast:" + name);
this.start();
}
public static PortType getPortType() {
return new PortType(PortType.SERIALIZATION_DATA,
PortType.COMMUNICATION_RELIABLE,
PortType.CONNECTION_MANY_TO_ONE, PortType.RECEIVE_AUTO_UPCALLS);
}
private synchronized SendPort getSendPort(int id) {
if (id == -1) {
logger.info("Ignoring " + id);
return null;
}
SendPort sp = sendports.get(id);
if (sp == null) {
// We're not connect to this ibis yet, so connect and store for
// later use.
// Test if the machine died recently to prevent us from trying to
// connect over and over again (this may be a problem since a single
// large mcast may be fragmented into many small packets, each with
// the same route containing the dead machine)
Long ripTime = diedmachines.get(id);
if (ripTime != null) {
long now = System.currentTimeMillis();
if (now - ripTime.longValue() > ZOMBIE_THRESHOLD) {
// the machine has been dead for a long time, but the sender
// insists it is still alive. Lets try again and see what
// happens.
diedmachines.remove(id);
logger.info("Sender insists that " + id
+ " is still allive, so I'll try again!");
} else {
logger.info("Ignoring " + id + " since it's dead!");
return null;
}
}
boolean failed = false;
IbisIdentifier ibisID = null;
try {
sp = ibis.base.createSendPort(LrmcIbis.additionalPortType);
ibisID = ibis.getId(id);
if (ibisID != null) {
sp.connect(ibisID, "LRMCRing-" + name, 10000, true);
sendports.put(id, sp);
} else {
logger.info("No Ibis at position " + id);
failed = true;
}
} catch (IOException e) {
failed = true;
logger.info("Got exception ", e);
}
if (failed) {
logger.info("Failed to connect to " + id
+ " - informing nameserver!");
// notify the nameserver that this machine may be dead...
try {
if (ibisID != null) {
ibis.registry().maybeDead(ibisID);
}
diedmachines.put(id, new Long(System.currentTimeMillis()));
} catch (Exception e2) {
logger.info("Failed to inform nameserver! " + e2);
// ignore
}
logger.info("Done informing nameserver");
return null;
}
}
return sp;
}
private void internalSend(Message m) {
SendPort sp = null;
if (m.destinationsUsed == 0) {
if (m.last) {
sp = getSendPort(m.sender);
if (sp != null) {
if (logger.isDebugEnabled()) {
logger.debug("Writing DONE message " + m.id
+ " to sender " + m.sender);
}
try {
WriteMessage wm = sp.newMessage();
wm.writeInt(-1);
wm.writeInt(m.id);
wm.finish();
} catch (IOException e) {
logger.debug("Writing DONE message to " + m.sender
+ " failed");
}
} else if (logger.isDebugEnabled()) {
logger.debug("No sendport for sender " + m.sender);
}
}
return;
}
// Get the next target from the destination array. If this fails, get
// the next one, etc. If no working destination is found we give up.
int index = 0;
int id = -1;
do {
id = m.destinations[index++];
sp = getSendPort(id);
if (sp == null) {
synchronized (this) {
if (finish) {
return;
}
}
}
} while (sp == null && index < m.destinationsUsed);
try {
if (sp == null) {
// No working destinations where found, so give up!
logger.info("No working destinations found, giving up!");
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Writing message " + m.id + "/" + m.num + " to "
+ id + ", sender " + m.sender
+ ", destinations left = "
+ (m.destinationsUsed - index));
}
// send the message to the target
WriteMessage wm = sp.newMessage();
m.write(wm, index);
bytes += wm.finish();
} catch (IOException e) {
logger.info("Write to " + id + " failed! ", e);
sendports.remove(id);
}
}
public void setDestination(IbisIdentifier[] destinations) {
logger.debug("setDestination called, destinations.length = "
+ destinations.length, new Throwable());
// We are allowed to change the order of machines in the destination
// array. This can be used to make the mcast 'cluster aware'.
IbisSorter.sort(ibis.identifier(), destinations);
this.destinations = new int[destinations.length];
for (int i = 0; i < destinations.length; i++) {
this.destinations[i] = ibis.getIbisID(destinations[i]);
logger.debug(" " + i + " (" + destinations[i] + " at "
+ destinations[i].location().getParent() + ") -> "
+ this.destinations[i]);
}
}
public long getBytes(boolean reset) {
long tmp = bytes;
if (reset) {
bytes = 0;
}
return tmp;
}
public boolean send(Message m) {
int[] destOld = m.destinations;
m.destinations = destinations;
m.destinationsUsed = destinations.length;
m.sender = ibis.myID;
m.local = true;
internalSend(m);
m.destinations = destOld;
return true;
}
public void run() {
while (true) {
Message m = sendQueue.dequeue();
if (m == null) {
// Someone wants us to stop
return;
}
try {
internalSend(m);
} catch (Exception e) {
logger.info("Sender thread got exception! ", e);
} finally {
cache.put(m);
}
}
}
public void done() {
// sendQueue.printTime();
synchronized (this) {
finish = true;
}
sendQueue.terminate();
try {
join(10000);
} catch (Exception e) {
// ignored
}
try {
receive.disableConnections();
int last = sendports.last();
for (int i = 0; i < last; i++) {
SendPort tmp = sendports.get(i);
if (tmp != null) {
tmp.close();
}
}
receive.close(1000);
} catch (Throwable e) {
// ignore, we tried...
}
}
public void upcall(ReadMessage rm) throws IOException {
Message message = null;
try {
int len = rm.readInt();
if (len == -1) {
// DONE message
int id = rm.readInt();
if (logger.isDebugEnabled()) {
logger.debug("Got DONE for message " + id);
}
receiver.gotDone(id);
return;
}
int dst = rm.readInt();
message = cache.get(len);
message.read(rm, len, dst);
if (logger.isDebugEnabled()) {
logger.debug("Reading message " + message.id + "/"
+ message.num + " from " + message.sender);
}
if (!message.local) {
message.refcount++;
try {
receiver.gotMessage(message);
} catch (Throwable e) {
logger.info("Delivery failed! ", e);
}
}
// Is this OK? sendQueue may block (is not allowed in upcall)!
// However, calling finish() here may change the message order,
// so we cannot do that. (Ceriel).
sendQueue.enqueue(message);
} catch (IOException e) {
logger.info("Failed to receive message: ", e);
rm.finish(e);
if (message != null) {
cache.put(message);
}
}
}
public int getPrefferedMessageSize() {
return cache.getPrefferedMessageSize();
}
}