package org.jctools.queues; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeThat; import java.util.ArrayList; import java.util.Collection; import java.util.Queue; import java.util.concurrent.atomic.AtomicBoolean; import org.jctools.queues.QueueSanityTest.Val; import org.jctools.queues.spec.ConcurrentQueueSpec; import org.jctools.queues.spec.Ordering; import org.jctools.queues.spec.Preference; import org.jctools.util.Pow2; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class MessagePassingQueueSanityTest { static final int SIZE = 8192 * 2; @Parameterized.Parameters public static Collection<Object[]> parameters() { ArrayList<Object[]> list = new ArrayList<Object[]>(); list.add(makeMpq(1, 1, 0, Ordering.FIFO, null));// unbounded SPSC list.add(makeMpq(0, 1, 0, Ordering.FIFO, null));// unbounded MPSC list.add(makeMpq(1, 1, 4, Ordering.FIFO, null));// SPSC size 4 list.add(makeMpq(1, 1, SIZE, Ordering.FIFO, null));// SPSC size SIZE list.add(makeMpq(1, 0, 1, Ordering.FIFO, null));// SPMC size 1 list.add(makeMpq(1, 0, SIZE, Ordering.FIFO, null));// SPMC size SIZE list.add(makeMpq(0, 1, 1, Ordering.FIFO, null));// MPSC size 1 list.add(makeMpq(0, 1, SIZE, Ordering.FIFO, null));// MPSC size SIZE list.add(makeMpq(0, 0, 2, Ordering.FIFO, null)); list.add(makeMpq(0, 0, SIZE, Ordering.FIFO, null)); return list; } private final MessagePassingQueue<Integer> queue; private final ConcurrentQueueSpec spec; public MessagePassingQueueSanityTest(ConcurrentQueueSpec spec, MessagePassingQueue<Integer> queue) { this.queue = queue; this.spec = spec; } @Before public void clear() { queue.clear(); } @Test(expected = NullPointerException.class) public void relaxedOfferNullResultsInNPE() { queue.relaxedOffer(null); } @Test public void sanity() { for (int i = 0; i < SIZE; i++) { assertNull(queue.relaxedPoll()); assertTrue(queue.isEmpty()); assertTrue(queue.size() == 0); } int i = 0; while (i < SIZE && queue.relaxedOffer(i)) i++; int size = i; assertEquals(size, queue.size()); if (spec.ordering == Ordering.FIFO) { // expect FIFO i = 0; Integer p; Integer e; while ((p = queue.relaxedPeek()) != null) { e = queue.relaxedPoll(); assertEquals(p, e); assertEquals(size - (i + 1), queue.size()); assertEquals(i++, e.intValue()); } assertEquals(size, i); } else { // expect sum of elements is (size - 1) * size / 2 = 0 + 1 + .... + (size - 1) int sum = (size - 1) * size / 2; i = 0; Integer e; while ((e = queue.relaxedPoll()) != null) { assertEquals(--size, queue.size()); sum -= e; } assertEquals(0, sum); } } int count = 0; Integer p; @Test public void sanityDrainBatch() { assertEquals(0, queue.drain(e -> { } , SIZE)); assertTrue(queue.isEmpty()); assertTrue(queue.size() == 0); count = 0; int i = queue.fill(() -> { return count++; } , SIZE); final int size = i; assertEquals(size, queue.size()); if (spec.ordering == Ordering.FIFO) { // expect FIFO p = queue.relaxedPeek(); count = 0; int drainCount = 0; i = 0; do { i += drainCount = queue.drain(e -> { // batch consumption can cause size to differ from following expectation // this is because elements are 'claimed' in a batch and their consumption lags if (spec.consumers == 1) { assertEquals(p, e); // peek will return the post claim peek assertEquals(size - (count + 1), queue.size()); // size will return the post claim size } assertEquals(count++, e.intValue()); p = queue.relaxedPeek(); }); } while (drainCount != 0); p = null; assertEquals(size, i); assertTrue(queue.isEmpty()); assertTrue(queue.size() == 0); } else { } } @Test public void testSizeIsTheNumberOfOffers() { int currentSize = 0; while (currentSize < SIZE && queue.relaxedOffer(currentSize)) { currentSize++; assertFalse(queue.isEmpty()); assertTrue(queue.size() == currentSize); } if (spec.isBounded()) { assertEquals(spec.capacity, currentSize); } else { assertEquals(SIZE, currentSize); } } @Test public void whenFirstInThenFirstOut() { assumeThat(spec.ordering, is(Ordering.FIFO)); // Arrange int i = 0; while (i < SIZE && queue.relaxedOffer(i)) { i++; } final int size = queue.size(); // Act i = 0; Integer prev; while ((prev = queue.relaxedPeek()) != null) { final Integer item = queue.relaxedPoll(); assertThat(item, is(prev)); assertEquals((size - (i + 1)), queue.size()); assertThat(item, is(i)); i++; } // Assert assertThat(i, is(size)); } @Test public void test_FIFO_PRODUCER_Ordering() throws Exception { assumeThat(spec.ordering, is(not((Ordering.FIFO)))); // Arrange int i = 0; while (i < SIZE && queue.relaxedOffer(i)) { i++; } int size = queue.size(); // Act // expect sum of elements is (size - 1) * size / 2 = 0 + 1 + .... + (size - 1) int sum = (size - 1) * size / 2; Integer e; while ((e = queue.relaxedPoll()) != null) { size--; assertEquals(size, queue.size()); sum -= e; } // Assert assertThat(sum, is(0)); } @Test public void whenOfferItemAndPollItemThenSameInstanceReturnedAndQueueIsEmpty() { assertTrue(queue.isEmpty()); assertTrue(queue.size() == 0); // Act final Integer e = new Integer(1876876); queue.relaxedOffer(e); assertFalse(queue.isEmpty()); assertEquals(1, queue.size()); final Integer oh = queue.relaxedPoll(); assertEquals(e, oh); // Assert assertTrue(queue.isEmpty()); assertTrue(queue.size() == 0); } @Test public void testPowerOf2Capacity() { assumeThat(spec.isBounded(), is(true)); int n = Pow2.roundToPowerOfTwo(spec.capacity); for (int i = 0; i < n; i++) { assertTrue("Failed to insert:" + i, queue.relaxedOffer(i)); } assertFalse(queue.relaxedOffer(n)); } static final class Val { public int value; } @Test public void testHappensBefore() throws Exception { final AtomicBoolean stop = new AtomicBoolean(); final MessagePassingQueue q = queue; final Val fail = new Val(); Thread t1 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { for (int i = 1; i <= 10; i++) { Val v = new Val(); v.value = i; q.relaxedOffer(v); } // slow down the producer, this will make the queue mostly empty encouraging visibility // issues. Thread.yield(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { for (int i = 0; i < 10; i++) { Val v = (Val) q.relaxedPeek(); if (v != null && v.value == 0) { fail.value = 1; stop.set(true); } q.relaxedPoll(); } } } }); t1.start(); t2.start(); Thread.sleep(1000); stop.set(true); t1.join(); t2.join(); assertEquals("reordering detected", 0, fail.value); } @Test public void testHappensBeforePrepetualDrain() throws Exception { final AtomicBoolean stop = new AtomicBoolean(); final MessagePassingQueue q = queue; final Val fail = new Val(); Thread t1 = new Thread(new Runnable() { int counter; @Override public void run() { while (!stop.get()) { for (int i = 1; i <= 10; i++) { Val v = new Val(); v.value = i; q.relaxedOffer(v); } // slow down the producer, this will make the queue mostly empty encouraging visibility // issues. Thread.yield(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { q.drain(e -> { Val v = (Val) e; if (v != null && v.value == 0) { fail.value = 1; stop.set(true); } } , e -> { return e; } , () -> { return !stop.get(); }); } } }); t1.start(); t2.start(); Thread.sleep(1000); stop.set(true); t1.join(); t2.join(); assertEquals("reordering detected", 0, fail.value); } @Test public void testHappensBeforePrepetualFill() throws Exception { final AtomicBoolean stop = new AtomicBoolean(); final MessagePassingQueue q = queue; final Val fail = new Val(); Thread t1 = new Thread(new Runnable() { int counter; @Override public void run() { counter = 1; q.fill(() -> { Val v = new Val(); v.value = 1 + (counter++ % 10); return v; } , e -> { return e; } , () -> { // slow down the producer, this will make the queue mostly empty encouraging visibility // issues. Thread.yield(); return !stop.get(); }); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { for (int i = 0; i < 10; i++) { Val v = (Val) q.relaxedPeek(); int r; if (v != null && (r = v.value) == 0) { fail.value = 1; stop.set(true); } q.relaxedPoll(); } } } }); t1.start(); t2.start(); Thread.sleep(1000); stop.set(true); t1.join(); t2.join(); assertEquals("reordering detected", 0, fail.value); } @Test public void testHappensBeforePrepetualFillDrain() throws Exception { final AtomicBoolean stop = new AtomicBoolean(); final MessagePassingQueue q = queue; final Val fail = new Val(); Thread t1 = new Thread(new Runnable() { int counter; @Override public void run() { counter = 1; q.fill(() -> { Val v = new Val(); v.value = 1 + (counter++ % 10); return v; } , e -> { return e; } , () -> { // slow down the producer, this will make the queue mostly empty encouraging // visibility issues. Thread.yield(); return !stop.get(); }); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { q.drain(e -> { Val v = (Val) e; if (v != null && v.value == 0) { fail.value = 1; stop.set(true); } } , e -> { return e; } , () -> { return !stop.get(); }); } } }); t1.start(); t2.start(); Thread.sleep(1000); stop.set(true); t1.join(); t2.join(); assertEquals("reordering detected", 0, fail.value); } @Test public void testSize() throws Exception { assumeThat(spec.isBounded(), is(true)); final AtomicBoolean stop = new AtomicBoolean(); final MessagePassingQueue<Integer> q = queue; final Val fail = new Val(); Thread t1 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { q.relaxedOffer(1); q.relaxedPoll(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { int size = q.size(); if(size != 0 && size != 1) { fail.value++; } } } }); t1.start(); t2.start(); Thread.sleep(1000); stop.set(true); t1.join(); t2.join(); assertEquals("Unexpected size observed", 0, fail.value); } static Object[] makeMpq(int producers, int consumers, int capacity, Ordering ordering, Queue<Integer> q) { ConcurrentQueueSpec spec = new ConcurrentQueueSpec(producers, consumers, capacity, ordering, Preference.NONE); if (q == null) { q = QueueFactory.newQueue(spec); } return new Object[] { spec, q }; } }