org.jgroups.protocols.RingBufferBundler 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.protocols;
import org.jgroups.Address;
import org.jgroups.Global;
import org.jgroups.Message;
import org.jgroups.annotations.Property;
import org.jgroups.util.RingBuffer;
import org.jgroups.util.Runner;
import org.jgroups.util.Util;
import java.util.Objects;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BiConsumer;
/**
* Bundler which uses {@link RingBuffer} to store messages. The difference to {@link TransferQueueBundler} is that
* RingBuffer uses a wait strategy (to for example spinning) before blocking. Also, the hashmap of the superclass is not
* used, but the array of the RingBuffer is used directly to bundle and send messages, minimizing memory allocation.
*/
public class RingBufferBundler extends BaseBundler {
protected RingBuffer rb;
protected Runner bundler_thread;
@Property(description="Number of spins before a real lock is acquired")
protected int num_spins=40; // number of times we call Thread.yield before acquiring the lock (0 disables)
protected static final String THREAD_NAME="RingBufferBundler";
protected BiConsumer wait_strategy=SPIN_PARK;
protected final Runnable run_function=this::readMessages;
protected static final BiConsumer SPIN=(it,spins) -> {;};
protected static final BiConsumer YIELD=(it,spins) -> Thread.yield();
protected static final BiConsumer PARK=(it,spins) -> LockSupport.parkNanos(1);
protected static final BiConsumer SPIN_PARK=(it, spins) -> {
if(it < spins/10)
return; // spin for the first 10% of all iterations, then switch to park()
LockSupport.parkNanos(1);
};
protected static final BiConsumer SPIN_YIELD=(it, spins) -> {
if(it < spins/10)
return; // spin for the first 10% of the total number of iterations
Thread.yield(); //, then switch to yield()
};
public RingBufferBundler() {
}
protected RingBufferBundler(RingBuffer rb) {
this.rb=rb;
this.capacity=rb.capacity();
}
public RingBufferBundler(int capacity) {
this(new RingBuffer<>(Message.class, assertPositive(capacity, "bundler capacity cannot be " + capacity)));
}
public RingBuffer buf() {return rb;}
public Thread getThread() {return bundler_thread.getThread();}
public int size() {return rb.size();}
public int getQueueSize() {return rb.size();}
public int numSpins() {return num_spins;}
public RingBufferBundler numSpins(int n) {num_spins=n; return this;}
@Property(description="The wait strategy: spin, yield, park, spin-park, spin-yield",writable=false)
public String waitStrategy() {return print(wait_strategy);}
@Property
public RingBufferBundler waitStrategy(String st) {wait_strategy=createWaitStrategy(st, YIELD); return this;}
public void init(TP transport) {
super.init(transport);
if(rb == null) {
rb=new RingBuffer<>(Message.class, assertPositive(capacity, "bundler capacity cannot be " + capacity));
this.capacity=rb.capacity();
}
bundler_thread=new Runner(transport.getThreadFactory(), THREAD_NAME, run_function, () -> rb.clear());
}
public void start() {
bundler_thread.start();
}
public void stop() {
bundler_thread.stop();
}
public void send(Message msg) throws Exception {
rb.put(msg);
}
/** Read and send messages in range [read-index .. read-index+available_msgs-1] */
public void sendBundledMessages(final Message[] buf, final int read_index, final int available_msgs) {
byte[] cluster_name=transport.cluster_name.chars();
int start=read_index;
final int end=index(start + available_msgs-1); // index of the last message to be read
for(;;) {
Message msg=buf[start];
if(msg == null) {
if(start == end)
break;
start=advance(start);
continue;
}
Address dest=msg.getDest();
try {
output.position(0);
Util.writeMessageListHeader(dest, msg.getSrc(), cluster_name, 1, output, dest == null);
// remember the position at which the number of messages (an int) was written, so we can later set the
// correct value (when we know the correct number of messages)
int size_pos=output.position() - Global.INT_SIZE;
int num_msgs=marshalMessagesToSameDestination(dest, buf, start, end, max_size);
if(num_msgs > 1) {
int current_pos=output.position();
output.position(size_pos);
output.writeInt(num_msgs);
output.position(current_pos);
}
transport.doSend(output.buffer(), 0, output.position(), dest);
if(transport.statsEnabled())
transport.getMessageStats().incrNumBatchesSent(num_msgs);
}
catch(Exception ex) {
log.trace("failed to send message(s) to %s: %s", dest == null? "group" : dest, ex.getMessage());
}
if(start == end)
break;
start=advance(start);
}
}
// Iterate through the following messages and find messages to the same destination (dest) and write them to output
protected int marshalMessagesToSameDestination(Address dest, Message[] buf,
int start_index, final int end_index, int max_bundle_size) throws Exception {
int num_msgs=0, bytes=0;
for(;;) {
Message msg=buf[start_index];
if(msg != null && Objects.equals(dest, msg.getDest())) {
int size=msg.size() + Global.SHORT_SIZE;
if(bytes + size > max_bundle_size)
break;
bytes+=size;
num_msgs++;
buf[start_index]=null;
output.writeShort(msg.getType());
msg.writeToNoAddrs(msg.getSrc(), output, transport.getId());
}
if(start_index == end_index)
break;
start_index=advance(start_index);
}
return num_msgs;
}
protected void readMessages() {
try {
int available_msgs=rb.waitForMessages(num_spins, wait_strategy);
int read_index=rb.readIndexLockless();
Message[] buf=rb.buf();
sendBundledMessages(buf, read_index, available_msgs);
rb.publishReadIndex(available_msgs);
}
catch(Throwable t) {
;
}
}
protected final int advance(int index) {return index+1 == capacity? 0 : index+1;}
protected final int index(int idx) {return idx & (capacity-1);} // fast equivalent to %
protected static String print(BiConsumer wait_strategy) {
if(wait_strategy == null) return null;
if(wait_strategy == SPIN) return "spin";
else if(wait_strategy == YIELD) return "yield";
else if(wait_strategy == PARK) return "park";
else if(wait_strategy == SPIN_PARK) return "spin-park";
else if(wait_strategy == SPIN_YIELD) return "spin-yield";
else return wait_strategy.getClass().getSimpleName();
}
protected BiConsumer createWaitStrategy(String st, BiConsumer default_wait_strategy) {
if(st == null) return default_wait_strategy;
switch(st) {
case "spin": return wait_strategy=SPIN;
case "yield": return wait_strategy=YIELD;
case "park": return wait_strategy=PARK;
case "spin_park":
case "spin-park": return wait_strategy=SPIN_PARK;
case "spin_yield":
case "spin-yield": return wait_strategy=SPIN_YIELD;
default:
try {
Class> clazz=(Class>)Util.loadClass(st, this.getClass());
return clazz.getDeclaredConstructor().newInstance();
}
catch(Throwable t) {
log.error("failed creating wait_strategy " + st, t);
return default_wait_strategy;
}
}
}
protected static int assertPositive(int value, String message) {
if(value <= 0) throw new IllegalArgumentException(message);
return value;
}
}