package lsr.paxos.network;
import static lsr.common.ProcessDescriptor.processDescriptor;
import java.util.BitSet;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import lsr.paxos.messages.Message;
import lsr.paxos.messages.MessageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides methods to communicate with other processes (replicas).
* It allows to send the message to one or many replicas, and provides listeners
* called every time new message is received or sent.
*
*/
public abstract class Network {
protected static final int localId = processDescriptor.localId;
public final static BitSet OTHERS = OTHERS_initializer();
private static BitSet OTHERS_initializer() {
BitSet bs = new BitSet(processDescriptor.numReplicas);
bs.set(0, processDescriptor.numReplicas);
bs.clear(processDescriptor.localId);
return bs;
}
public Network() {
}
protected abstract void send(Message message, int destination);
protected abstract void send(Message message, BitSet destinations);
public abstract void start();
/**
* Sends the message to process with specified id.
*
* @param message the message to send
* @param destination the id of replica to send message to
*/
final public void sendMessage(Message message, int destination) {
assert destination != localId : "sending unicast to self";
BitSet bs = new BitSet();
bs.set(destination);
send(message, destination);
fireSentMessage(message, bs);
}
/**
* Sends the message to processes with specified ids as a bitset
*
* @param message the message to send
* @param destinations bit set with marked replica id's to send message to
*/
final public void sendMessage(Message message, BitSet destinations) {
assert message != null : "Null message";
assert !destinations.isEmpty() : "Sending a message to noone";
assert !destinations.get(localId) : "sending to self is inefficient";
if (logger.isTraceEnabled()) {
logger.trace(
"Sending with {} message {} to {}",
this.getClass().getName().substring(
this.getClass().getName().lastIndexOf('.') + 1), message, destinations);
}
send(message, destinations);
fireSentMessage(message, destinations);
}
/**
* Sends the message to all processes but the sender
*
* @param message the message to send
*/
final public void sendToOthers(Message message) {
sendMessage(message, OTHERS);
}
/**
* Adds a new message listener for a certain type of message or all messages
* ( see {@link MessageType}). The listener cannot be added twice for the
* same message - this causes a {@link RuntimeException}.
*/
final public static void addMessageListener(MessageType mType, MessageHandler handler) {
boolean wasAdded = false;
synchronized (msgListeners) {
if (mType == MessageType.ANY) {
for (Entry<MessageType, CopyOnWriteArrayList<MessageHandler>> entry : msgListeners.entrySet()) {
if (entry.getKey() == MessageType.ANY || entry.getKey() == MessageType.SENT)
continue;
wasAdded = entry.getValue().addIfAbsent(handler);
if (!wasAdded) {
throw new RuntimeException("Handler already registered");
}
}
} else {
CopyOnWriteArrayList<MessageHandler> handlers = msgListeners.get(mType);
wasAdded = handlers.addIfAbsent(handler);
}
}
if (!wasAdded) {
throw new RuntimeException("Handler already registered");
}
}
/**
* Removes a previously registered listener. Throws {@link RuntimeException}
* if the listener is not on list.
*/
final public static void removeMessageListener(MessageType mType, MessageHandler handler) {
boolean wasPresent = false;
synchronized (msgListeners) {
if (mType == MessageType.ANY) {
for (Entry<MessageType, CopyOnWriteArrayList<MessageHandler>> entry : msgListeners.entrySet()) {
if (entry.getKey() == MessageType.ANY || entry.getKey() == MessageType.SENT)
continue;
wasPresent |= entry.getValue().remove(handler);
}
} else {
CopyOnWriteArrayList<MessageHandler> handlers = msgListeners.get(mType);
wasPresent = handlers.remove(handler);
}
}
if (!wasPresent) {
throw new RuntimeException("Handler not registered");
}
}
// // // // // // // // // // // // // // // // // //
/**
* For each message type, keeps a list of it's listeners.
*
* The list is shared between networks
*/
private static final Map<MessageType, CopyOnWriteArrayList<MessageHandler>> msgListeners;
static {
msgListeners = Collections.synchronizedMap(
new EnumMap<MessageType, CopyOnWriteArrayList<MessageHandler>>(MessageType.class));
for (MessageType ms : MessageType.values()) {
msgListeners.put(ms, new CopyOnWriteArrayList<MessageHandler>());
}
}
/**
* Notifies all active network listeners that new message was received.
*/
protected final void fireReceiveMessage(Message message, int sender) {
assert message.getType() != MessageType.SENT && message.getType() != MessageType.ANY;
if (logger.isTraceEnabled()) {
StackTraceElement[] st = Thread.currentThread().getStackTrace();
String className = st[st.length - 2].getClassName().substring(
st[st.length - 2].getClassName().lastIndexOf('.') + 1);
logger.trace("Received from [p{}] by {} message {}", sender, className, message);
}
boolean handled = broadcastToListeners(message.getType(), message, sender);
if (!handled) {
logger.warn("Unhandled message: " + message);
}
}
/**
* Notifies all active network listeners that message was sent.
*/
private final void fireSentMessage(Message msg, BitSet dest) {
List<MessageHandler> handlers = msgListeners.get(MessageType.SENT);
for (MessageHandler listener : handlers) {
listener.onMessageSent(msg, dest);
}
if (logger.isTraceEnabled()) {
StackTraceElement[] st = Thread.currentThread().getStackTrace();
String stel = st[st.length - 2].toString();
logger.trace("Sending message {} to {} from {}", msg, dest, stel);
}
}
/**
* Informs all listeners waiting for the message type about the message.
* Parameter type is needed in order to support MessageType.ANY value.
* Returns if there was at least one listener.
*/
private final boolean broadcastToListeners(MessageType type, Message msg, int sender) {
List<MessageHandler> handlers = msgListeners.get(type);
boolean handled = false;
for (MessageHandler listener : handlers) {
listener.onMessageReceived(msg, sender);
handled = true;
}
return handled;
}
private final static Logger logger = LoggerFactory.getLogger(Network.class);
}