All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgroups.stack.NakReceiverWindow Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 35.0.0.Beta1
Show newest version



package org.jgroups.stack;

import org.jgroups.Address;
import org.jgroups.Message;
import org.jgroups.annotations.GuardedBy;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.RetransmitTable;
import org.jgroups.util.TimeScheduler;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
 * Keeps track of messages according to their sequence numbers. Allows
 * messages to be added out of order, and with gaps between sequence numbers.
 * Method remove() removes the first message with a sequence
 * number that is 1 higher than next_to_remove (this variable is
 * then incremented), or it returns null if no message is present, or if no
 * message's sequence number is 1 higher.
 * 

* When there is a gap upon adding a message, its seqno will be added to the * Retransmitter, which (using a timer) requests retransmissions of missing * messages and keeps on trying until the message has been received, or the * member who sent the message is suspected. * * There are 2 variables which keep track of messages: *

    *
  • highest_delivered: the highest delivered seqno, updated on remove(). The next message to be removed is highest_delivered + 1 *
  • highest_received: the highest received message, updated on add (if a new message is added, not updated e.g. * if a missing msg was received) *
*

* Note that the first seqno expected is 1. This design is described in doc/design.NAKACK.txt *

* Example: * 1,2,3,5,6,8: highest_delivered=2 (or 3, depending on whether remove() was called !), highest_received=8 * * @author Bela Ban */ public class NakReceiverWindow { public interface Listener { void missingMessageReceived(long seqno, Address original_sender); void messageGapDetected(long from, long to, Address src); } private final Lock lock=new ReentrantLock(); private volatile boolean running=true; /** The highest delivered seqno, updated on remove(). The next message to be removed is highest_delivered + 1 */ @GuardedBy("lock") private long highest_delivered=0; /** The highest received message, updated on add (if a new message is added, not updated e.g. if a missing msg * was received) */ @GuardedBy("lock") private long highest_received=0; /** * ConcurrentMap. Maintains messages keyed by (sorted) sequence numbers */ private final RetransmitTable xmit_table; private final AtomicBoolean processing=new AtomicBoolean(false); /** if not set, no retransmitter thread will be started. Useful if * protocols do their own retransmission (e.g PBCAST) */ private Retransmitter retransmitter=null; private Listener listener=null; protected static final Log log=LogFactory.getLog(NakReceiverWindow.class); /** The highest stable() seqno received */ long highest_stability_seqno=0; /** * Creates a new instance with the given retransmit command * * @param sender The sender associated with this instance * @param cmd The command used to retransmit a missing message, will * be invoked by the table. If null, the retransmit thread will not be started * @param highest_delivered_seqno The next seqno to remove is highest_delivered_seqno +1 * @param sched the external scheduler to use for retransmission * requests of missing msgs. If it's not provided or is null, an internal */ public NakReceiverWindow(Address sender, Retransmitter.RetransmitCommand cmd, long highest_delivered_seqno, TimeScheduler sched) { this(sender, cmd, highest_delivered_seqno, sched, true); } public NakReceiverWindow(Address sender, Retransmitter.RetransmitCommand cmd, long highest_delivered_seqno, TimeScheduler sched, boolean use_range_based_retransmitter) { this(sender, cmd, highest_delivered_seqno, sched, use_range_based_retransmitter, 5, 10000, 1.2, 5 * 60 * 1000, false); } public NakReceiverWindow(final Address sender, Retransmitter.RetransmitCommand cmd, long highest_delivered_seqno, TimeScheduler sched, boolean use_range_based_retransmitter, int num_rows, int msgs_per_row, double resize_factor, long max_compaction_time, boolean automatic_purging) { highest_delivered=highest_delivered_seqno; highest_received=highest_delivered; if(sched == null) throw new IllegalStateException("timer has to be provided and cannot be null"); if(cmd != null) { retransmitter=use_range_based_retransmitter? new RangeBasedRetransmitter(sender, cmd, sched) : new DefaultRetransmitter(sender, cmd, sched); } xmit_table=new RetransmitTable(num_rows, msgs_per_row, highest_delivered, resize_factor, max_compaction_time, automatic_purging); } public AtomicBoolean getProcessing() { return processing; } public void setRetransmitTimeouts(Interval timeouts) { retransmitter.setRetransmitTimeouts(timeouts); } public void setXmitStaggerTimeout(long timeout) { if(retransmitter != null) retransmitter.setXmitStaggerTimeout(timeout); } public void setListener(Listener l) { this.listener=l; } public int getPendingXmits() { return retransmitter!= null? retransmitter.size() : 0; } // No need to acquire the lock as this method is called via JMX, and stale values are acceptable. Ditto for the next // few methods below public int getRetransmitTableSize() {return xmit_table.size();} public int getRetransmitTableCapacity() {return xmit_table.capacity();} public double getRetransmitTableFillFactor() {return xmit_table.getFillFactor();} public long getRetransmitTableOffset() {return xmit_table.getOffset();} public void compact() { lock.lock(); try { xmit_table.compact(); } finally { lock.unlock(); } } /** * Adds a message according to its seqno (sequence number). *

* There are 4 cases where messages are added: *

    *
  1. seqno is the next to be expected seqno: added to map *
  2. seqno is <= highest_delivered: discard as we've already delivered it *
  3. seqno is smaller than the next expected seqno: missing message, add it *
  4. seqno is greater than the next expected seqno: add it to map and fill the gaps with null messages * for retransmission. Add the seqno to the retransmitter too *
* @return True if the message was added successfully, false otherwise (e.g. duplicate message) */ public boolean add(final long seqno, final Message msg) { long old_next, next_to_add; boolean missing_msg_received=false; lock.lock(); try { if(!running) return false; next_to_add=highest_received +1; old_next=next_to_add; // Case #1: we received the expected seqno: most common path if(seqno == next_to_add) { xmit_table.put(seqno, msg); return true; } // Case #2: we received a message that has already been delivered: discard it if(seqno <= highest_delivered) { if(log.isTraceEnabled()) log.trace("seqno " + seqno + " is smaller than " + next_to_add + "); discarding message"); return false; } // Case #3: we finally received a missing message. Case #2 handled seqno <= highest_delivered, so this // seqno *must* be between highest_delivered and next_to_add if(seqno < next_to_add) { Message existing=xmit_table.putIfAbsent(seqno, msg); if(existing != null) return false; // key/value was present retransmitter.remove(seqno); missing_msg_received=true; if(log.isTraceEnabled()) log.trace(new StringBuilder("added missing msg ").append(msg.getSrc()).append('#').append(seqno)); return true; } // Case #4: we received a seqno higher than expected: add to Retransmitter if(seqno > next_to_add) { xmit_table.put(seqno, msg); retransmitter.add(old_next, seqno -1); if(listener != null) { try {listener.messageGapDetected(next_to_add, seqno, msg.getSrc());} catch(Throwable t) {} } return true; } } finally { highest_received=Math.max(highest_received, seqno); lock.unlock(); if(listener != null && missing_msg_received) { try {listener.missingMessageReceived(seqno, msg.getSrc());} catch(Throwable t) {} } } return true; } public Message remove() { return remove(true, false); } public Message remove(boolean acquire_lock, boolean remove_msg) { Message retval; if(acquire_lock) lock.lock(); try { long next=highest_delivered +1; retval=remove_msg? xmit_table.remove(next) : xmit_table.get(next); if(retval != null) { // message exists and is ready for delivery highest_delivered=next; return retval; } return null; } finally { if(acquire_lock) lock.unlock(); } } /** * Removes as many messages as possible * @return List A list of messages, or null if no available messages were found */ public List removeMany(final AtomicBoolean processing) { return removeMany(processing, false, 0); } /** * Removes as many messages as possible * @param remove_msgs Removes messages from xmit_table * @param max_results Max number of messages to remove in one batch * @return List A list of messages, or null if no available messages were found */ public List removeMany(final AtomicBoolean processing, boolean remove_msgs, int max_results) { List retval=null; int num_results=0; lock.lock(); try { while(true) { long next=highest_delivered +1; Message msg=remove_msgs? xmit_table.remove(next) : xmit_table.get(next); if(msg != null) { // message exists and is ready for delivery highest_delivered=next; if(retval == null) retval=new LinkedList<>(); retval.add(msg); if(max_results <= 0 || ++num_results < max_results) continue; } if((retval == null || retval.isEmpty()) && processing != null) processing.set(false); return retval; } } finally { lock.unlock(); } } /** * Delete all messages <= seqno (they are stable, that is, have been delivered by all members). * Stop when a number > seqno is encountered (all messages are ordered on seqnos). */ public void stable(long seqno) { lock.lock(); try { if(seqno > highest_delivered) { if(log.isWarnEnabled()) log.warn("seqno " + seqno + " is > highest_delivered (" + highest_delivered + ";) ignoring stability message"); return; } xmit_table.purge(seqno); // we need to remove all seqnos *including* seqno // remove all seqnos below (and including) seqno from retransmission /** We don't need to remove a range from the retransmitter, as the messages in the range will always be empty: - When we get a stable() message, it'll only include seqnos that were *delivered* by everyone - To get a seqno delivered, all seqnos below it must have been delivered (no gaps) - Therefore all seqnos below seqno are non-null, and were either never in a retransmitter, or got removed from the retransmitter when a missing message was received. ==> The call below is therefore unneeded, as it will never remove any seqnos from a retransmitter ! belaban Aug 2011 // retransmitter.remove(seqno, true); **/ highest_stability_seqno=Math.max(highest_stability_seqno, seqno); } finally { lock.unlock(); } } /** * Destroys the NakReceiverWindow. After this method returns, no new messages can be added and a new * NakReceiverWindow should be used instead. Note that messages can still be removed though. */ public void destroy() { lock.lock(); try { running=false; retransmitter.reset(); xmit_table.clear(); highest_delivered=highest_received=highest_stability_seqno=0; } finally { lock.unlock(); } } /** Returns the lowest, highest delivered and highest received seqnos */ public long[] getDigest() { lock.lock(); try { return new long[]{highest_delivered, highest_received}; } finally { lock.unlock(); } } /** Returns the highest sequence number of a message consumed by the application (by remove()). * Note that this is different from the highest deliverable seqno. E.g. in 23,24,26,27,29, the highest * delivered message may be 22, whereas the highest deliverable message may be 24 ! * @return the highest sequence number of a message consumed by the * application (by remove()) */ public long getHighestDelivered() { lock.lock(); try { return highest_delivered; } finally { lock.unlock(); } } public long setHighestDelivered(long new_val) { lock.lock(); try { long retval=highest_delivered; highest_delivered=new_val; return retval; } finally { lock.unlock(); } } /** * Returns the highest sequence number received so far (which may be * higher than the highest seqno delivered so far; e.g., for * 1,2,3,5,6 it would be 6. * * @see NakReceiverWindow#getHighestDelivered */ public long getHighestReceived() { lock.lock(); try { return highest_received; } finally { lock.unlock(); } } /** * Returns the message from xmit_table * @param seqno * @return Message from xmit_table */ public Message get(long seqno) { lock.lock(); try { return xmit_table.get(seqno); } finally { lock.unlock(); } } /** * Returns a list of messages in the range [from .. to], including from and to * @param from * @param to * @return A list of messages, or null if none in range [from .. to] was found */ public List get(long from, long to) { lock.lock(); try { return xmit_table.get(from, to); } finally { lock.unlock(); } } public int size() { lock.lock(); try { return xmit_table.size(); } finally { lock.unlock(); } } /** * Returns the number of bytes taken up by all of the messages in the RetransmitTable * @param include_headers * @return */ public long sizeOfAllMessages(boolean include_headers) { lock.lock(); try { return xmit_table.sizeOfAllMessages(include_headers); } finally { lock.unlock(); } } public int getMissingMessages() { lock.lock(); try { return xmit_table.getNullMessages(highest_delivered, highest_received); } finally { lock.unlock(); } } public String toString() { lock.lock(); try { return printMessages(); } finally { lock.unlock(); } } /** * Prints xmit_table. Requires read lock to be present * @return String */ protected String printMessages() { StringBuilder sb=new StringBuilder(); lock.lock(); try { sb.append('[').append(highest_delivered).append(" (").append(highest_received).append(")"); if(xmit_table != null && !xmit_table.isEmpty()) { int non_received=xmit_table.getNullMessages(highest_delivered, highest_received); sb.append(" (size=").append(xmit_table.size()).append(", missing=").append(non_received). append(", highest stability=").append(highest_stability_seqno).append(')'); } sb.append(']'); return sb.toString(); } finally { lock.unlock(); } } public String printLossRate() { StringBuilder sb=new StringBuilder(); int num_missing=getPendingXmits(); int num_received=size(); int total=num_missing + num_received; sb.append("total=").append(total).append(" (received=").append(num_received).append(", missing=") .append(num_missing).append(")"); return sb.toString(); } public String printRetransmitStats() { return retransmitter instanceof RangeBasedRetransmitter? ((RangeBasedRetransmitter)retransmitter).printStats() : "n/a"; } /* ------------------------------- Private Methods -------------------------------------- */ /* --------------------------- End of Private Methods ----------------------------------- */ }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy