package org.jgroups.util; import java.lang.reflect.Array; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; /** * Ring buffer of fixed capacity designed for multiple writers but only a single reader. Advancing the read or * write index blocks until it is possible to do so. * @author Bela Ban * @since 4.0 */ public class RingBuffer<T> { protected final T[] buf; protected int ri, wi; // read and write indices protected int count; // number of elements available to be read protected final Lock lock=new ReentrantLock(); protected final Condition not_empty=lock.newCondition(); // reader can block on this protected final Condition not_full=lock.newCondition(); // writes can block on this public RingBuffer(Class<T> element_type, int capacity) { int c=Util.getNextHigherPowerOfTwo(capacity); // power of 2 for faster mod operation buf=(T[])Array.newInstance(element_type, c); } public T[] buf() {return buf;} public int capacity() {return buf.length;} public int readIndexLockless() {return ri;} public int countLockLockless() {return count;} public int readIndex() { lock.lock(); try { return ri; } finally { lock.unlock(); } } public int writeIndex() { lock.lock(); try { return wi; } finally { lock.unlock(); } } public int count() { lock.lock(); try { return count; } finally { lock.unlock(); } } /** * Tries to add a new element at the current write index and advances the write index. If the write index is at the * same position as the read index, this will block until the read index is advanced. * @param element the element to be added. Must not be null, or else this operation returns immediately without * adding the null element */ public RingBuffer<T> put(T element) throws InterruptedException { if(element == null) return this; lock.lock(); try { while(count == buf.length) not_full.await(); buf[wi]=element; if(++wi == buf.length) wi=0; count++; not_empty.signal(); return this; } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lock(); try { while(count == 0) not_empty.await(); T el=buf[ri]; buf[ri]=null; if(++ri == buf.length) ri=0; count--; not_full.signal(); return el; } finally { lock.unlock(); } } public RingBuffer<T> publishReadIndex(int num_elements_read) { // this.ri is only read/written by the consumer, and since there's only 1 consumer, there's no need to synchronize on it this.ri=realIndex(this.ri + num_elements_read); lock.lock(); try { this.count-=num_elements_read; not_full.signalAll(); // wake up all writers return this; } finally { lock.unlock(); } } /** Blocks until messages are available */ public int waitForMessages() throws InterruptedException { return waitForMessages(40, null); } /** * Blocks until messages are available * @param num_spins the number of times we should spin before acquiring a lock * @param wait_strategy the strategy used to spin. The first parameter is the iteration count and the second * parameter is the max number of spins */ public int waitForMessages(int num_spins, final BiConsumer<Integer,Integer> wait_strategy) throws InterruptedException { // try spinning first (experimental) for(int i=0; i < num_spins && count == 0; i++) { if(wait_strategy != null) wait_strategy.accept(i, num_spins); else Thread.yield(); } if(count == 0) { lock.lock(); try { while(count == 0) not_empty.await(); } finally { lock.unlock(); } } return count; // whatever is the last count; could have been updated since lock release } public RingBuffer<T> clear() { lock.lock(); try { count=ri=wi=0; for(int i=0; i < buf.length; i++) buf[i]=null; return this; } finally { lock.unlock(); } } public int size() { lock.lock(); try { return count; } finally { lock.unlock(); } } public boolean isEmpty() { lock.lock(); try { return ri == wi; } finally { lock.unlock(); } } public String toString() { return String.format("[ri=%d wi=%d size=%d cap=%d]", ri, wi, size(), buf.length); } /** Apparently much more efficient than mod (%) */ protected int realIndex(int index) { return index & (buf.length -1); } }