org.jgroups.util.RequestTable Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS 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).
package org.jgroups.util;
import org.jgroups.annotations.GuardedBy;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.LongStream;
/**
* Table for storing requests associated with monotonically increasing sequence numbers (seqnos).
* Could be used for example in {@link org.jgroups.blocks.RequestCorrelator}. Grows and shrinks when needed.
* Addition is always at the end, yielding monotonically increasing seqnos. Removal is done by nulling the element(s)
* between low and high and advancing the low pointer whenever possible.
* See JGRP-1982 for details.
* @author Bela Ban
* @since 3.6.7
*/
public class RequestTable {
protected T[] buffer; // the ring buffer
protected long low; // pointing to the next element to be removed; low is always <= high
protected long high; // pointing to the next element to be added; high is >= low
protected int removes_till_compaction; // number of removes before attempt compaction (0 disables this)
protected int num_removes; // current number of removes
protected final Lock lock=new ReentrantLock(); // to synchronize modifications
public interface Visitor {
boolean visit(T element);
}
public RequestTable(final int capacity) {
this(capacity, 0, 0);
}
public RequestTable(final int capacity, long low, long high) {
int len=Util.getNextHigherPowerOfTwo(capacity);
this.buffer=(T[])new Object[len];
this.low=low;
this.high=high;
}
public long low() {return low;}
public long high() {return high;}
public int capacity() {return buffer.length;}
public int index(long seqno) {return (int)((seqno) & (capacity()-1));}
public int removesTillCompaction() {return removes_till_compaction;}
public RequestTable removesTillCompaction(int rems) {this.removes_till_compaction=rems; return this;}
/**
* Adds a new element and returns the sequence number at which it was inserted. Advances the high
* pointer and grows the buffer if needed.
* @param element the element to be added. Must not be null or an exception will be thrown
* @return the seqno at which element was added
*/
public long add(T element) {
lock.lock();
try {
long next=high+1;
if(next - low > capacity())
_grow(next-low);
int high_index=index(high);
buffer[high_index]=element;
return high++;
}
finally {
lock.unlock();
}
}
public T get(long seqno) {
lock.lock();
try {
int index=index(seqno);
return buffer[index];
}
finally {
lock.unlock();
}
}
/**
* Removes the element at the index matching seqno. If seqno == low, tries to advance low until a non-null element
* is encountered, up to high
* @param seqno
* @return
*/
public T remove(long seqno) {
lock.lock();
try {
if(seqno < low || seqno > high)
return null;
int index=index(seqno);
T retval=buffer[index];
if(retval != null && removes_till_compaction > 0)
num_removes++;
buffer[index]=null;
if(seqno == low)
advanceLow();
if(removes_till_compaction > 0 && num_removes >= removes_till_compaction) {
_compact();
num_removes=0;
}
return retval;
}
finally {
lock.unlock();
}
}
/**
* Removes all elements in the stream. Calls the consumer (if not null) on non-null elements
*/
public RequestTable removeMany(LongStream seqnos, Consumer consumer) {
if(seqnos == null) return this;
AtomicBoolean advance=new AtomicBoolean(false);
seqnos.forEach(seqno -> {
T element=null;
lock.lock();
try {
if(seqno < low || seqno > high)
return;
int index=index(seqno);
if((element=buffer[index]) != null && removes_till_compaction > 0)
num_removes++;
buffer[index]=null;
if(seqno == low)
advance.set(true);
}
finally {
lock.unlock();
}
if(consumer != null)
consumer.accept(element);
});
lock.lock();
try {
if(advance.get())
advanceLow();
if(removes_till_compaction > 0 && num_removes >= removes_till_compaction) {
_compact();
num_removes=0;
}
}
finally {
lock.unlock();
}
return this;
}
/** Removes all elements, compacts the buffer and sets low=high=0 */
public RequestTable clear() {return clear(0);}
public RequestTable clear(long mark) {
lock.lock();
try {
low=high=mark;
buffer=(T[])new Object[2];
return this;
}
finally {
lock.unlock();
}
}
public RequestTable forEach(Visitor visitor) {
if(visitor == null) return null;
lock.lock();
try {
for(long i=low, num_iterations=0; i < high && num_iterations < buffer.length; i++, num_iterations++) {
int index=index(i);
T el=buffer[index];
if(!visitor.visit(el))
break;
}
return this;
}
finally {
lock.unlock();
}
}
/**
* Non-blocking alternative to {@link #forEach(Visitor)}: iteration is performed on the array that exists at the
* time of this call. Changes to the underlying array will not be reflected in the iteration.
* @param visitor the {@link Visitor}.
*/
public RequestTable forEachNonBlocking(Visitor visitor) {
if(visitor == null) return null;
T[] buf;
long lo, hi;
lock.lock();
try {
buf=this.buffer; lo=this.low; hi=this.high;
}
finally {
lock.unlock();
}
for(long i=lo, num_iterations=0; i < hi && num_iterations < buf.length; i++, num_iterations++) {
int index=index(i);
T el=buf[index];
if(!visitor.visit(el))
break;
}
return this;
}
/**
* Grows the array to at least new_capacity. This method is mainly used for testing and is not typically called
* directly, but indirectly when adding elements and the underlying array has no space left.
* @param new_capacity the new capacity of the underlying array. Will be rounded up to the nearest power of 2 value.
* A value smaller than the current capacity is ignored.
*/
public RequestTable grow(int new_capacity) {
lock.lock();
try {
_grow(new_capacity);
return this;
}
finally {
lock.unlock();
}
}
/**
* Shrinks the underlying array to half its size _if_ the new array can hold all of the existing elements.
* @return true if the compaction succeeded, or false if it failed (e.g. not enough space)
*/
public boolean compact() {
lock.lock();
try {
return _compact();
}
finally {
lock.unlock();
}
}
/**
* Checks if there is at least buffer.length/2 contiguous space in range [low+1 .. high-1] available
*/
public boolean contiguousSpaceAvailable() {
lock.lock();
try {
return _contiguousSpaceAvailable(buffer.length >> 1);
}
finally {
lock.unlock();
}
}
/**
* Returns the number of non-null elements in range [low .. high-1]
* @return
*/
public int size() {
int retval=0;
for(long i=low, num_iterations=0; i < high && num_iterations < buffer.length; i++, num_iterations++) {
int index=index(i);
if(buffer[index] != null)
retval++;
}
return retval;
}
public String toString() {
return String.format("low=%d high=%d cap=%d, %d element(s)", low, high, buffer.length, size());
}
@GuardedBy("lock")
protected void _grow(long new_capacity) {
int new_cap=Util.getNextHigherPowerOfTwo((int)Math.max(buffer.length, new_capacity));
if(new_cap == buffer.length)
return;
_copy(new_cap);
}
/**
* Shrinks the array to half of its current size if the current number of elements fit into half of the capacity.
* @return true if the compaction succeeded, else false (e.g. when the current elements would not fit)
*/
@GuardedBy("lock")
protected boolean _compact() {
int new_cap=buffer.length >> 1; // needs to be a power of 2 for efficient modulo operation, e.g. for index()
// boolean compactable=this.buffer.length > 0 && (size() <= new_cap || (contiguousSpaceAvailable=_contiguousSpaceAvailable(new_cap)));
boolean compactable=this.buffer.length > 0 && high-low <= new_cap;
if(!compactable)
return false; // not enough space to shrink the buffer to half its size
_copy(new_cap);
return true;
}
public String dumpContents() {
StringBuilder sb=new StringBuilder();
lock.lock();
try {
int new_cap=buffer.length >> 1;
for(long i=low, num_iterations=0; i < high && num_iterations < buffer.length; i++, num_iterations++) {
int index=index(i);
T el=buffer[index];
if(el != null) {
long hash=el.hashCode();
int small_idx=index(i, new_cap);
sb.append(String.format("seqno %d: index: %d val: %d, index in %d-buffer: %d\n", i, index, hash, new_cap, small_idx));
}
}
}
finally {
lock.unlock();
}
return sb.toString();
}
/** Copies elements from old into new array */
protected void _copy(int new_cap) {
// copy elements from [low to high-1] into new indices in new array
T[] new_buf=(T[])new Object[new_cap];
int new_len=new_buf.length;
int old_len=this.buffer.length;
for(long i=low, num_iterations=0; i < high && num_iterations < old_len; i++, num_iterations++) {
int old_index=index(i, old_len);
if(this.buffer[old_index] != null) {
int new_index=index(i, new_len);
new_buf[new_index]=this.buffer[old_index];
}
}
this.buffer=new_buf;
}
/**
* Check if we have at least space_needed contiguous free slots available in range [low+1 .. high-1]
* @param space_needed the number of contiguous free slots required to do compaction, usually half of the current
* buffer size
* @return true if a contiguous space was found, false otherwise
*/
@GuardedBy("lock")
protected boolean _contiguousSpaceAvailable(int space_needed) {
int num_slots_scanned=0;
int size_of_contiguous_area=0;
if(high-low-1 < space_needed)
return false;
for(long i=low+1; i < high; i++) {
num_slots_scanned++;
int index=index(i);
if(this.buffer[index] == null) {
if(++size_of_contiguous_area >= space_needed)
return true;
}
else {
size_of_contiguous_area=0;
// we scanned more than half of the current array and found an occupied slot, so there is no chance of
// finding space_needed contiguous free slots as we have less than half of the current array to scan
if(num_slots_scanned > space_needed || high-i-1 < space_needed)
return false;
}
}
return false;
}
protected int highestContiguousSpaceAvailable() {
int size_of_current_contiguous_area=0;
int highest=0;
for(long i=low+1; i < high; i++) {
int index=index(i);
if(this.buffer[index] == null)
size_of_current_contiguous_area++;
else {
highest=Math.max(highest, size_of_current_contiguous_area);
size_of_current_contiguous_area=0;
}
}
return Math.max(highest, size_of_current_contiguous_area);
}
@GuardedBy("lock")
protected void advanceLow() {
while(low < high) {
int index=index(low);
if(buffer[index] != null)
break;
low++;
}
}
protected static int index(long seqno, int length) {return (int)((seqno) & length-1);}
}