/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jctools.queues.intrusive; import org.jctools.queues.QueueFactory; import org.jctools.queues.atomic.AtomicQueueFactory; 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; import java.util.Collection; import java.util.Collections; import java.util.Queue; import java.util.concurrent.atomic.AtomicBoolean; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeThat; public class IntrusiveQueueSanityTest { static final int SIZE = 8192 * 2; TestNode[] nodes = new TestNode[SIZE]; private final MpscIntrusiveLinkedQueue queue = new MpscIntrusiveLinkedQueue(); private final ConcurrentQueueSpec spec = new ConcurrentQueueSpec(0, 1, 0, Ordering.FIFO, Preference.NONE); @Before public void clear() { for (int i = 0; i < SIZE; i++) { nodes[i] = new TestNode(); nodes[i].value = i; } queue.clear(); } @Test public void sanity() { for (int i = 0; i < SIZE; i++) { assertNull(queue.poll()); assertTrue(queue.isEmpty()); assertEquals(0, queue.size()); } int i = 0; while (i < SIZE && queue.offer(nodes[i])) i++; int size = i; assertEquals(size, queue.size()); if (spec.ordering == Ordering.FIFO) { // expect FIFO i = 0; Node p; TestNode e; while ((p = queue.peek()) != null) { e = (TestNode) queue.poll(); assertEquals(p, e); assertEquals(size - (i + 1), queue.size()); assertEquals(i++, e.value); } assertEquals(size, i); } else { // expect sum of elements is (size - 1) * size / 2 = 0 + 1 + .... + (size - 1) int sum = (size - 1) * size / 2; TestNode e; while ((e = (TestNode) queue.poll()) != null) { assertEquals(--size, queue.size()); sum -= e.value; } assertEquals(0, sum); } } @Test public void testSizeIsTheNumberOfOffers() { int currentSize = 0; while (currentSize < SIZE && queue.offer(nodes[currentSize])) { currentSize++; assertEquals(currentSize, queue.size()); } } @Test public void whenFirstInThenFirstOut() { assumeThat(spec.ordering, is(Ordering.FIFO)); // Arrange for (int i = 0; i < SIZE; i++) { nodes[i].value = i; queue.offer(nodes[i]); } final int size = queue.size(); // Act int i = 0; Node prev; while ((prev = queue.peek()) != null) { final TestNode item = (TestNode) queue.poll(); assertThat(item, is(prev)); assertEquals(size - (i + 1), queue.size()); assertThat(item.value, is(i)); i++; } // Assert assertThat(i, is(size)); } @Test(expected=NullPointerException.class) public void offerNullResultsInNPE(){ queue.offer(null); } @Test public void whenOfferItemAndPollItemThenSameInstanceReturnedAndQueueIsEmpty() { assertTrue(queue.isEmpty()); assertEquals(0, queue.size()); // Act final Integer e = 1876876; nodes[0].value = e; queue.offer(nodes[0]); assertFalse(queue.isEmpty()); assertEquals(1, queue.size()); TestNode retNode = (TestNode) queue.poll(); final Integer oh = retNode.value; assertEquals(e, oh); assertSame(nodes[0], retNode); // Assert assertThat(retNode, sameInstance(nodes[0])); assertTrue(queue.isEmpty()); assertEquals(0, queue.size()); } @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.offer(nodes[i])); } assertFalse(queue.offer(new TestNode())); fail(); } static final class Val { public int value; } @Test public void testHappensBefore() throws Exception { final AtomicBoolean stop = new AtomicBoolean(); final MpscIntrusiveLinkedQueue 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++) { TestNode v = new TestNode(); v.value = i; q.offer(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++) { TestNode v = (TestNode) q.peek(); if (v != null && v.value == 0) { fail.value = 1; stop.set(true); System.out.println("v = " + v); } q.poll(); } } } }); 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 { final AtomicBoolean stop = new AtomicBoolean(); final MpscIntrusiveLinkedQueue q = queue; final Val fail = new Val(); Thread t1 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { q.offer(nodes[0]); q.poll(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (!stop.get()) { int size = q.size(); if(size != 0 && size != 1) { fail.value = size; } } } }); t1.start(); t2.start(); Thread.sleep(1000); stop.set(true); t1.join(); t2.join(); assertEquals("Unexpected size observed", 0, fail.value); } public static Object[] makeQueue(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 }; } public static Object[] makeAtomic(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 = AtomicQueueFactory.newQueue(spec); } return new Object[] { spec, q }; } }