package org.jgroups.tests; import org.jgroups.Global; import org.jgroups.Message; import org.jgroups.util.AverageMinMax; import org.jgroups.util.MessageBatch; import org.jgroups.util.Util; import org.testng.annotations.Test; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** Tests multiple producers adding multiple elements to the queue (message batch) and one of them becoming the single * consumer which removes elements for as long as possible, then terminating. The algorithm needs to ensure that there * aren't any elements left in the queue when all producers and the single consumer have terminated. The Pluscal code * for this algorithm is at https://github.com/belaban/pluscal/blob/master/MessageBatchDrainTest.tla. * @author Bela Ban * @since 4.0 */ @Test(groups=Global.FUNCTIONAL,singleThreaded=true) public class MessageBatchDrainTest { protected final Lock lock=new ReentrantLock(); protected final MessageBatch batch=new MessageBatch(BATCH_SIZE); protected final AtomicInteger counter=new AtomicInteger(0); protected final LongAdder added=new LongAdder(); protected final LongAdder removed=new LongAdder(); protected final LongAdder num_removers=new LongAdder(); protected final AverageMinMax avg_removed=new AverageMinMax(); protected final AverageMinMax avg_remove_loops=new AverageMinMax(); protected static final boolean RESIZE=false; protected static final int BATCH_SIZE=200; public void testDraining() throws InterruptedException { MyThread[] threads=new MyThread[10]; final CountDownLatch latch=new CountDownLatch(1); for(int i=0; i < threads.length; i++) { threads[i]=new MyThread(latch); threads[i].start(); } latch.countDown(); Util.sleep(5000); System.out.printf("\nStopping threads\n"); for(MyThread thread: threads) thread.cancel(); System.out.printf("done, joining threads\n"); for(MyThread thread: threads) thread.join(); System.out.printf("\ncounter=%d, added=%d, removed=%d, avg_removed=%s, avg_remove_loops=%s (removers=%d)\n", counter.get(), added.sum(), removed.sum(), avg_removed, avg_remove_loops, num_removers.sum()); assert added.sum() == removed.sum(); assert counter.get() == 0; assert this.batch.isEmpty(); } protected void add(Message msg) { int size=_add(msg); if(size > 0) { added.increment(); drain(); } } protected void add(MessageBatch mb) { int size=_add(mb); if(size > 0) { added.add(size); drain(); } } protected void drain() { if(counter.getAndIncrement() == 0) { num_removers.increment(); int cnt=0, removed_msgs, total_removed=0; do { removed_msgs=_clear(); total_removed+=removed_msgs; removed.add(removed_msgs); cnt++; // LockSupport.parkNanos(4_000); } while(counter.decrementAndGet() != 0); avg_remove_loops.add(cnt); avg_removed.add(total_removed); } } protected int _add(Message msg) { lock.lock(); try { return this.batch.add(msg, RESIZE); } finally { lock.unlock(); } } protected int _add(MessageBatch b) { lock.lock(); try { return this.batch.add(b, RESIZE); } finally { lock.unlock(); } } protected int _clear() { lock.lock(); try { int size=batch.size(); batch.clear(); return size; } finally { lock.unlock(); } } protected class MyThread extends Thread { protected final CountDownLatch latch; protected volatile boolean running=true; public MyThread(CountDownLatch latch) { this.latch=latch; } protected void cancel() {running=false;} public void run() { try { latch.await(); } catch(InterruptedException e) { e.printStackTrace(); } while(running) { if(Util.tossWeightedCoin(.3)) add(new Message()); else { Message[] msgs=create(10); MessageBatch mb=new MessageBatch(Arrays.asList(msgs)); add(mb); } } } } protected Message[] create(int max) { int num=(int)Util.random(max); Message[] msgs=new Message[num]; for(int i=0; i < msgs.length; i++) msgs[i]=new Message(); return msgs; } }