org.jgroups.stack.NakReceiverWindow Maven / Gradle / Ivy
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
*/
@Deprecated
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:
*
* - seqno is the next to be expected seqno: added to map
*
- seqno is <= highest_delivered: discard as we've already delivered it
*
- seqno is smaller than the next expected seqno: missing message, add it
*
- 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