/* * ModeShape (http://www.modeshape.org) * * 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.modeshape.common.collection.ring; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.junit.Before; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.common.statistic.Stopwatch; /** * @author Randall Hauch (rhauch@redhat.com) */ public class RingBufferTest { protected static final Random RANDOM = new Random(); protected volatile boolean print = false; protected volatile boolean slightPausesInConsumers = false; @Before public void beforeEach() { print = false; } @Test public void shouldBuildWithNoGarbageCollection() { Executor executor = Executors.newCachedThreadPool(); RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(8) .garbageCollect(false).build(); print = false; // Add 10 entries with no consumers ... long value = 0L; for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); } // Add a single consumer that should start seeing items 10 and up ... MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); ringBuffer.addConsumer(consumer1); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } ringBuffer.shutdown(); print(""); print("Ring buffer shutdown completed"); assertTrue(consumer1.isClosed()); } @Test public void shouldBuildWithGarbageCollectionAnd8Entries() { Executor executor = Executors.newCachedThreadPool(); RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(8) .garbageCollect(true).build(); print = false; // Add 10 entries with no consumers ... long value = 0L; for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); } // Add a single consumer that should start seeing items 10 and up ... MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); ringBuffer.addConsumer(consumer1); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } ringBuffer.shutdown(); print(""); print("Ring buffer shutdown completed"); // assertTrue(consumer1.isClosed()); } @Test public void shouldBuildWithGarbageCollectionAnd1024Entries() { Executor executor = Executors.newCachedThreadPool(); RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(1024) .garbageCollect(true).build(); print = false; // Add 10 entries with no consumers ... long value = 0L; for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); } // Add a single consumer that should start seeing items 10 and up ... MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); ringBuffer.addConsumer(consumer1); // Add 10 more entries ... for (int i = 0; i != 1000; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } ringBuffer.shutdown(); print(""); print("Ring buffer shutdown completed"); // assertTrue(consumer1.isClosed()); } @Test public void shouldBeAbleToAddAndRemoveConsumers() throws Exception { Executor executor = Executors.newCachedThreadPool(); RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(8) .build(); print = false; // Add 10 entries with no consumers ... long value = 0L; for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); } // Add a single consumer that should start seeing items 10 and up ... MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); ringBuffer.addConsumer(consumer1); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } // Add a second consumer that should start seeing items 20 and up ... MonotonicallyIncreasingConsumer consumer2 = new MonotonicallyIncreasingConsumer("second", 20L, 20L, 0); ringBuffer.addConsumer(consumer2); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } ringBuffer.remove(consumer2); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } assertTrue(consumer2.isClosed()); ringBuffer.shutdown(); print(""); print("Ring buffer shutdown completed"); assertTrue(consumer1.isClosed()); assertTrue(consumer2.isClosed()); } @Test // @Ignore( "Takes a long time to run" ) public void consumersShouldSeeEventsInCorrectOrder() throws Exception { Executor executor = Executors.newCachedThreadPool(); RingBuffer<Long, MonotonicallyIncreasingConsumer> ringBuffer = RingBufferBuilder.withSingleProducer(executor, LongConsumerAdapter.INSTANCE) .ofSize(8).garbageCollect(false).build(); print = false; // Add 10 entries with no consumers ... long value = 0L; for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); } // Add a single consumer that should start seeing items 10 and up ... MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); ringBuffer.addConsumer(consumer1); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } // Add a single consumer that should start seeing items 10 and up ... MonotonicallyIncreasingConsumer consumer2 = new MonotonicallyIncreasingConsumer("second", 20L, 20L, 0); ringBuffer.addConsumer(consumer2); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } // Add a second consumer that should start seeing items 20 and up ... MonotonicallyIncreasingConsumer consumer3 = new MonotonicallyIncreasingConsumer("third", 30L, 30L, 0); ringBuffer.addConsumer(consumer3); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } // Add a second consumer that should start seeing items 20 and up ... MonotonicallyIncreasingConsumer consumer4 = new MonotonicallyIncreasingConsumer("fourth", 40L, 40L, 0); ringBuffer.addConsumer(consumer4); // Add 10 more entries ... for (int i = 0; i != 10; ++i) { print("Adding entry " + value); ringBuffer.add(value++); // Thread.sleep(100L); } // print = true; slightPausesInConsumers = false; boolean slightPauseBetweenEvents = false; // Add 400K more entries Stopwatch sw = new Stopwatch(); int count = 2000; sw.start(); for (int i = 0; i != count; ++i) { ringBuffer.add(value++); if (slightPauseBetweenEvents) { Thread.sleep(RANDOM.nextInt(50)); } } sw.stop(); // Do 10 more while printing ... for (int i = 0; i != 10; ++i) { // print = true; print("Adding entry " + value); ringBuffer.add(value++); } ringBuffer.shutdown(); print(""); print("Ring buffer shutdown completed"); assertTrue(consumer1.isClosed()); assertTrue(consumer2.isClosed()); assertTrue(consumer3.isClosed()); assertTrue(consumer4.isClosed()); --value; assertThat(consumer1.getLastValue(), is(value)); assertThat(consumer2.getLastValue(), is(value)); assertThat(consumer3.getLastValue(), is(value)); assertThat(consumer4.getLastValue(), is(value)); print(""); print("Time to add " + count + " entries: " + sw.getAverageDuration()); } @Test @FixFor( "MODE-2195" ) public void shouldAutomaticallySetTheBufferSizeToTheNextPowerOf2() throws Exception { Executor executor = Executors.newCachedThreadPool(); RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(5) .garbageCollect(false).build(); assertEquals(8, ringBuffer.getBufferSize()); ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(1023).garbageCollect(false).build(); assertEquals(1024, ringBuffer.getBufferSize()); } protected void print( String message ) { if (print) System.out.println(message); } protected class MonotonicallyIncreasingConsumer extends Consumer<Long> { private final String id; private boolean first = true; private long lastValue = -1L; private long lastPosition = -1L; private boolean closed = false; private final int secondsToWork; public MonotonicallyIncreasingConsumer( String id, long firstValue, long firstPosition, int secondsToWork ) { this.id = id; this.lastValue = firstValue; this.lastPosition = firstPosition; this.secondsToWork = secondsToWork; } @Override public boolean consume( Long entry, long position, long max ) { assertTrue(!closed); print(id + " consuming " + entry.longValue() + " at position " + position + " with max " + max); try { Thread.sleep(secondsToWork * 1000); } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); } if (slightPausesInConsumers && position % 1000 == 0) { try { Thread.sleep(RANDOM.nextInt(100)); } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); } } if (first) { assertTrue(entry.longValue() == lastValue); assertTrue(position == lastPosition); first = false; } else { assertTrue(entry.longValue() == (lastValue + 1)); lastValue = entry.longValue(); assertTrue(position == (lastPosition + 1)); lastPosition = position; } return true; } @Override public void close() { super.close(); print(id + " closing"); closed = true; } public long getLastPosition() { return lastPosition; } public long getLastValue() { return lastValue; } public boolean isClosed() { return closed; } } private static class LongConsumerAdapter implements RingBuffer.ConsumerAdapter<Long, MonotonicallyIncreasingConsumer> { protected static final LongConsumerAdapter INSTANCE = new LongConsumerAdapter(); private LongConsumerAdapter() { } @Override public boolean consume( MonotonicallyIncreasingConsumer consumer, Long event, long position, long maxPosition ) { consumer.consume(event, position, maxPosition); return true; } @Override public void close( MonotonicallyIncreasingConsumer consumer ) { consumer.close(); } @Override public void handleException( MonotonicallyIncreasingConsumer consumer, Throwable t, Long entry, long position, long maxPosition ) { throw new AssertionError("Test failure", t); } } }