package org.jgroups.protocols.tom; import org.jgroups.Message; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * The implementation of the Delivery Manager * * @author Pedro Ruivo * @since 3.1 */ public class DeliveryManagerImpl implements DeliveryManager { private static final MessageInfoComparator COMPARATOR = new MessageInfoComparator(); private final SortedSet<MessageInfo> deliverySet= new TreeSet<MessageInfo>(COMPARATOR); private final ConcurrentMap<MessageID, MessageInfo> messageCache = new ConcurrentHashMap<MessageID, MessageInfo>(8192, .75f, 64); private final Set<Message> singleDestinationSet = new HashSet<Message>(); /** * Add a new group message to be deliver * @param messageID the message ID * @param message the message (needed to be deliver later) * @param sequenceNumber the initial sequence number */ public void addNewMessageToDeliver(MessageID messageID, Message message, long sequenceNumber) { MessageInfo messageInfo = new MessageInfo(messageID, message, sequenceNumber); synchronized (deliverySet) { deliverySet.add(messageInfo); } messageCache.put(messageID, messageInfo); } /** * marks the message as ready to deliver and set the final sequence number (to be ordered) * @param messageID the message ID * @param finalSequenceNumber the final sequence number */ public void markReadyToDeliver(MessageID messageID, long finalSequenceNumber) { markReadyToDeliverV2(messageID, finalSequenceNumber); } @SuppressWarnings({"SuspiciousMethodCalls"}) private void markReadyToDeliverV1(MessageID messageID, long finalSequenceNumber) { //This is an old version. It was the bottleneck. Updated to version 2. It can be removed later synchronized (deliverySet) { MessageInfo messageInfo = null; boolean needsUpdatePosition = false; Iterator<MessageInfo> iterator = deliverySet.iterator(); while (iterator.hasNext()) { MessageInfo aux = iterator.next(); if (aux.equals(messageID)) { messageInfo = aux; if (messageInfo.sequenceNumber != finalSequenceNumber) { needsUpdatePosition = true; iterator.remove(); } break; } } if (messageInfo == null) { throw new IllegalStateException("Message ID not found in to deliver list. this can't happen. " + "Message ID is " + messageID); } messageInfo.updateAndmarkReadyToDeliver(finalSequenceNumber); if (needsUpdatePosition) { deliverySet.add(messageInfo); } if (!deliverySet.isEmpty() && deliverySet.first().isReadyToDeliver()) { deliverySet.notify(); } } } private void markReadyToDeliverV2(MessageID messageID, long finalSequenceNumber) { MessageInfo messageInfo = messageCache.remove(messageID); if (messageInfo == null) { throw new IllegalStateException("Message ID not found in to deliver list. this can't happen. " + "Message ID is " + messageID); } boolean needsUpdatePosition = messageInfo.isUpdatePositionNeeded(finalSequenceNumber); synchronized (deliverySet) { if (needsUpdatePosition) { deliverySet.remove(messageInfo); messageInfo.updateAndmarkReadyToDeliver(finalSequenceNumber); deliverySet.add(messageInfo); } else { messageInfo.updateAndmarkReadyToDeliver(finalSequenceNumber); } if (deliverySet.first().isReadyToDeliver()) { deliverySet.notify(); } } } //see the interface javadoc @Override public List<Message> getNextMessagesToDeliver() throws InterruptedException { LinkedList<Message> toDeliver = new LinkedList<Message>(); synchronized (deliverySet) { while (deliverySet.isEmpty() && singleDestinationSet.isEmpty()) { deliverySet.wait(); } if (!singleDestinationSet.isEmpty()) { toDeliver.addAll(singleDestinationSet); singleDestinationSet.clear(); return toDeliver; } if (!deliverySet.first().isReadyToDeliver()) { deliverySet.wait(); } if (!singleDestinationSet.isEmpty()) { toDeliver.addAll(singleDestinationSet); singleDestinationSet.clear(); } Iterator<MessageInfo> iterator = deliverySet.iterator(); while (iterator.hasNext()) { MessageInfo messageInfo = iterator.next(); if (messageInfo.isReadyToDeliver()) { toDeliver.add(messageInfo.getMessage()); iterator.remove(); } else { break; } } } return toDeliver; } /** * remove all the pending messages */ public void clear() { synchronized (deliverySet) { deliverySet.clear(); messageCache.clear(); } } /** * delivers a message that has only as destination member this node * * @param msg the message */ public void deliverSingleDestinationMessage(Message msg) { synchronized (deliverySet) { singleDestinationSet.add(msg); deliverySet.notify(); } } /** * Keeps the state of a message */ private static class MessageInfo { private MessageID messageID; private Message message; private volatile long sequenceNumber; private volatile boolean readyToDeliver; public MessageInfo(MessageID messageID, Message message, long sequenceNumber) { if (messageID == null) { throw new NullPointerException("Message ID can't be null"); } this.messageID = messageID; this.message = message.copy(true, true); this.sequenceNumber = sequenceNumber; this.readyToDeliver = false; this.message.setSrc(messageID.getAddress()); } private Message getMessage() { return message; } private void updateAndmarkReadyToDeliver(long finalSequenceNumber) { this.readyToDeliver = true; this.sequenceNumber = finalSequenceNumber; } private boolean isReadyToDeliver() { return readyToDeliver; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null) { return false; } boolean isMessageID = o.getClass() == MessageID.class; if (o.getClass() != getClass() && !isMessageID) { return false; } if (isMessageID) { return messageID.equals(o); } MessageInfo that = (MessageInfo) o; return messageID.equals(that.messageID); } @Override public int hashCode() { return messageID.hashCode(); } @Override public String toString() { return "MessageInfo{" + "messageID=" + messageID + ", sequenceNumber=" + sequenceNumber + ", readyToDeliver=" + readyToDeliver + '}'; } public boolean isUpdatePositionNeeded(long finalSequenceNumber) { return sequenceNumber != finalSequenceNumber; } } private static class MessageInfoComparator implements Comparator<MessageInfo> { @Override public int compare(MessageInfo messageInfo, MessageInfo messageInfo1) { if (messageInfo == null) { return messageInfo1 == null ? 0 : 1; } else if (messageInfo1 == null) { return -1; } int compareMessageID = messageInfo.messageID.compareTo(messageInfo1.messageID); if (compareMessageID == 0) { return 0; } if (messageInfo.sequenceNumber != messageInfo1.sequenceNumber) { return Long.signum(messageInfo.sequenceNumber - messageInfo1.sequenceNumber); } return compareMessageID; } } /** * It is used for testing (see the messages in JMX) * @return unmodifiable set of messages */ public Set<MessageInfo> getMessageSet() { synchronized (deliverySet) { return Collections.unmodifiableSet(deliverySet); } } }