package org.jgroups.tests; import org.jgroups.Global; import org.jgroups.Message; import org.jgroups.util.AverageMinMax; import org.jgroups.util.Util; import org.testng.annotations.Test; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.LongAdder; /** * Tests an algorithm that has multiple threads adding to a queue but only one thread at a time consuming the queue's * elements, using a simple CAS counter and a (lock-synchronized) queue. The Pluscal code is at * https://github.com/belaban/pluscal/blob/master/add.tla * @author Bela Ban * @since 4.0 */ @Test(groups=Global.FUNCTIONAL,singleThreaded=true) public class DrainTest { protected final Queue<Message> queue=new ArrayBlockingQueue<Message>(50000); 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(); 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 (removers=%d)\n", counter.get(), added.sum(), removed.sum(), avg_removed, num_removers.sum()); assert added.sum() == removed.sum(); assert counter.get() == 0; assert this.queue.isEmpty(); } protected void add(Message msg) { if(queue.offer(msg)) added.increment(); drain(); } protected void drain() { if(counter.getAndIncrement() == 0) { num_removers.increment(); int cnt=0; do { Message msg=queue.poll(); if(msg == null) continue; // System.err.printf("got empty message"); removed.increment(); cnt++; } while(counter.decrementAndGet() != 0); avg_removed.add(cnt); } } 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) { Message msg=new Message(); add(msg); } } } 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; } }