org.jgroups.util.RequestTable Maven / Gradle / Ivy
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);}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy