![JAR search and dependency download from the Maven repository](/logo.png)
bboss.org.jgroups.stack.AckReceiverWindow Maven / Gradle / Ivy
The newest version!
package bboss.org.jgroups.stack;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceArray;
import bboss.org.jgroups.Message;
import bboss.org.jgroups.util.Tuple;
/**
* Counterpart of AckSenderWindow. Simple FIFO buffer.
* Every message received is ACK'ed (even duplicates) and added to a hashmap
* keyed by seqno. The next seqno to be received is stored in next_to_remove
. When a message with
* a seqno less than next_to_remove is received, it will be discarded. The remove()
method removes
* and returns a message whose seqno is equal to next_to_remove, or null if not found.
* Change May 28 2002 (bela): replaced TreeSet with HashMap. Keys do not need to be sorted, and adding a key to
* a sorted set incurs overhead.
*
* @author Bela Ban
* @version $Id: AckReceiverWindow.java,v 1.51 2010/03/23 16:09:54 belaban Exp $
*/
public class AckReceiverWindow {
private final AtomicLong next_to_remove;
private final AtomicBoolean processing=new AtomicBoolean(false);
private final ConcurrentMap segments=new ConcurrentHashMap(64, 0.75F, 64);
private volatile Segment current_segment=null;
private volatile Segment current_remove_segment=null;
private final int segment_capacity;
private long highest_segment_created=0;
public static final Message TOMBSTONE=new Message(false) {
public String toString() {
return "tombstone";
}
};
public AckReceiverWindow(long initial_seqno) {
this(initial_seqno, 20000);
}
public AckReceiverWindow(long initial_seqno, int segment_capacity) {
next_to_remove=new AtomicLong(initial_seqno);
this.segment_capacity=segment_capacity;
long index=next_to_remove.get() / segment_capacity;
long first_seqno=(next_to_remove.get() / segment_capacity) * segment_capacity;
this.segments.put(index, new Segment(first_seqno, segment_capacity));
Segment initial_segment=findOrCreateSegment(next_to_remove.get());
current_segment=initial_segment;
current_remove_segment=initial_segment;
for(long i=0; i < next_to_remove.get(); i++) {
initial_segment.add(i, TOMBSTONE);
initial_segment.remove(i);
}
}
public AtomicBoolean getProcessing() {
return processing;
}
/** Adds a new message. Message cannot be null
* @return True if the message was added, false if not (e.g. duplicate, message was already present)
*/
public boolean add(long seqno, Message msg) {
return add2(seqno, msg) == 1;
}
/**
* Adds a message if not yet received
* @param seqno
* @param msg
* @return -1 if not added because seqno < next_to_remove, 0 if not added because already present,
* 1 if added successfully
*/
public byte add2(long seqno, Message msg) {
Segment segment=current_segment;
if(segment == null || !segment.contains(seqno)) {
segment=findOrCreateSegment(seqno);
if(segment != null)
current_segment=segment;
}
if(segment == null)
return -1;
return segment.add(seqno, msg);
}
/**
* Removes a message whose seqno is equal to next_to_remove
, increments the latter. Returns message
* that was removed, or null, if no message can be removed. Messages are thus removed in order.
*/
public Message remove() {
long next=next_to_remove.get();
Segment segment=current_remove_segment;
if(segment == null || !segment.contains(next)) {
segment=findSegment(next);
if(segment != null)
current_remove_segment=segment;
}
if(segment == null)
return null;
Message retval=segment.remove(next);
if(retval != null) {
next_to_remove.compareAndSet(next, next +1);
if(segment.allRemoved())
segments.remove(next / segment_capacity);
}
return retval;
}
/**
* Removes as many messages as possible (in sequence, without gaps)
* @param max Max number of messages to be removed
* @return Tuple,Long>: a tuple of the message list and the highest seqno removed
*/
public Tuple,Long> removeMany(final int max) {
List list=null; // we remove msgs.size() messages *max*
Tuple,Long> retval=null;
int count=0;
boolean looping=true;
while(count < max && looping) {
long next=next_to_remove.get();
Segment segment=current_remove_segment;
if(segment == null || !segment.contains(next)) {
segment=findSegment(next);
if(segment != null)
current_remove_segment=segment;
}
if(segment == null)
return retval;
long segment_id=next;
long end=segment.getEndIndex();
while(next < end && count < max) {
Message msg=segment.remove(next);
if(msg == null) {
looping=false;
break;
}
if(list == null) {
list=new LinkedList(); // we remove msgs.size() messages *max*
retval=new Tuple,Long>(list, 0L);
}
list.add(msg);
count++;
retval.setVal2(next);
next_to_remove.compareAndSet(next, ++next);
if(segment.allRemoved())
segments.remove(segment_id / segment_capacity);
}
}
return retval;
}
public List removeManyAsList(int max) {
Tuple, Long> tuple=removeMany(max);
return tuple != null? tuple.getVal1() : null;
}
public void reset() {
segments.clear();
}
public int size() {
int retval=0;
for(Segment segment: segments.values())
retval+=segment.size();
return retval;
}
public String toString() {
StringBuilder sb=new StringBuilder();
int size=size();
sb.append(size + " messages");
if(size <= 100)
sb.append(" in " + segments.size() + " segments");
return sb.toString();
}
public String printMessages() {
StringBuilder sb=new StringBuilder();
List keys=new LinkedList(segments.keySet());
Collections.sort(keys);
for(long key: keys) {
Segment segment=segments.get(key);
if(segment == null)
continue;
for(long i=segment.getStartIndex(); i < segment.getEndIndex(); i++) {
Message msg=segment.get(i);
if(msg == null)
continue;
if(msg == TOMBSTONE)
sb.append("T ");
else
sb.append(i + " ");
}
}
return sb.toString();
}
private Segment findOrCreateSegment(long seqno) {
long index=seqno / segment_capacity;
if(index > highest_segment_created) {
long start_seqno=seqno / segment_capacity * segment_capacity;
Segment segment=new Segment(start_seqno, segment_capacity);
Segment tmp=segments.putIfAbsent(index, segment);
if(tmp != null) // segment already exists
segment=tmp;
else
highest_segment_created=index;
return segment;
}
return segments.get(index);
}
private Segment findSegment(long seqno) {
long index=seqno / segment_capacity;
return segments.get(index);
}
private static class Segment {
final long start_index; // e.g. 5000. Then seqno 5100 would be at index 100
final int capacity;
final AtomicReferenceArray array;
final AtomicInteger num_tombstones=new AtomicInteger(0);
public Segment(long start_index, int capacity) {
this.start_index=start_index;
this.capacity=capacity;
this.array=new AtomicReferenceArray(capacity);
}
public long getStartIndex() {
return start_index;
}
public long getEndIndex() {
return start_index + capacity;
}
public boolean contains(long seqno) {
return seqno >= start_index && seqno < getEndIndex();
}
public Message get(long seqno) {
int index=index(seqno);
if(index < 0 || index >= array.length())
return null;
return array.get(index);
}
public byte add(long seqno, Message msg) {
int index=index(seqno);
if(index < 0)
return -1;
boolean success=array.compareAndSet(index, null, msg);
if(success) {
return 1;
}
else
return 0;
}
public Message remove(long seqno) {
int index=index(seqno);
if(index < 0)
return null;
Message retval=array.get(index);
if(retval != null && retval != TOMBSTONE && array.compareAndSet(index, retval, TOMBSTONE)) {
num_tombstones.incrementAndGet();
return retval;
}
return null;
}
public boolean allRemoved() {
return num_tombstones.get() >= capacity;
}
public String toString() {
return start_index + " - " + (start_index + capacity -1) + " (" + size() + " elements)";
}
public int size() {
int retval=0;
for(int i=0; i < capacity; i++) {
Message tmp=array.get(i);
if(tmp != null && tmp != TOMBSTONE)
retval++;
}
return retval;
}
private int index(long seqno) {
if(seqno < start_index)
return -1;
int index=(int)(seqno - start_index);
if(index < 0 || index >= capacity)
throw new IndexOutOfBoundsException("index=" + index + ", start_index=" + start_index + ", seqno=" + seqno);
return index;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy