001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.modeshape.common.collection.ring; 018 019import static org.hamcrest.core.Is.is; 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertThat; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024import java.util.Random; 025import java.util.concurrent.Executor; 026import java.util.concurrent.Executors; 027import org.junit.Before; 028import org.junit.Test; 029import org.modeshape.common.FixFor; 030import org.modeshape.common.statistic.Stopwatch; 031 032/** 033 * @author Randall Hauch (rhauch@redhat.com) 034 */ 035public class RingBufferTest { 036 protected static final Random RANDOM = new Random(); 037 038 protected volatile boolean print = false; 039 protected volatile boolean slightPausesInConsumers = false; 040 041 @Before 042 public void beforeEach() { 043 print = false; 044 } 045 046 @Test 047 public void shouldBuildWithNoGarbageCollection() { 048 Executor executor = Executors.newCachedThreadPool(); 049 RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(8) 050 .garbageCollect(false).build(); 051 print = false; 052 053 // Add 10 entries with no consumers ... 054 long value = 0L; 055 for (int i = 0; i != 10; ++i) { 056 print("Adding entry " + value); 057 ringBuffer.add(value++); 058 } 059 060 // Add a single consumer that should start seeing items 10 and up ... 061 MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); 062 ringBuffer.addConsumer(consumer1); 063 064 // Add 10 more entries ... 065 for (int i = 0; i != 10; ++i) { 066 print("Adding entry " + value); 067 ringBuffer.add(value++); 068 // Thread.sleep(100L); 069 } 070 071 ringBuffer.shutdown(); 072 print(""); 073 print("Ring buffer shutdown completed"); 074 assertTrue(consumer1.isClosed()); 075 } 076 077 @Test 078 public void shouldBuildWithGarbageCollectionAnd8Entries() { 079 Executor executor = Executors.newCachedThreadPool(); 080 RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(8) 081 .garbageCollect(true).build(); 082 print = false; 083 084 // Add 10 entries with no consumers ... 085 long value = 0L; 086 for (int i = 0; i != 10; ++i) { 087 print("Adding entry " + value); 088 ringBuffer.add(value++); 089 } 090 091 // Add a single consumer that should start seeing items 10 and up ... 092 MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); 093 ringBuffer.addConsumer(consumer1); 094 095 // Add 10 more entries ... 096 for (int i = 0; i != 10; ++i) { 097 print("Adding entry " + value); 098 ringBuffer.add(value++); 099 // Thread.sleep(100L); 100 } 101 102 ringBuffer.shutdown(); 103 print(""); 104 print("Ring buffer shutdown completed"); 105 // assertTrue(consumer1.isClosed()); 106 } 107 108 @Test 109 public void shouldBuildWithGarbageCollectionAnd1024Entries() { 110 Executor executor = Executors.newCachedThreadPool(); 111 RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(1024) 112 .garbageCollect(true).build(); 113 print = false; 114 115 // Add 10 entries with no consumers ... 116 long value = 0L; 117 for (int i = 0; i != 10; ++i) { 118 print("Adding entry " + value); 119 ringBuffer.add(value++); 120 } 121 122 // Add a single consumer that should start seeing items 10 and up ... 123 MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); 124 ringBuffer.addConsumer(consumer1); 125 126 // Add 10 more entries ... 127 for (int i = 0; i != 1000; ++i) { 128 print("Adding entry " + value); 129 ringBuffer.add(value++); 130 // Thread.sleep(100L); 131 } 132 133 ringBuffer.shutdown(); 134 print(""); 135 print("Ring buffer shutdown completed"); 136 // assertTrue(consumer1.isClosed()); 137 } 138 139 @Test 140 public void shouldBeAbleToAddAndRemoveConsumers() throws Exception { 141 Executor executor = Executors.newCachedThreadPool(); 142 RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(8) 143 .build(); 144 print = false; 145 146 // Add 10 entries with no consumers ... 147 long value = 0L; 148 for (int i = 0; i != 10; ++i) { 149 print("Adding entry " + value); 150 ringBuffer.add(value++); 151 } 152 153 // Add a single consumer that should start seeing items 10 and up ... 154 MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); 155 ringBuffer.addConsumer(consumer1); 156 157 // Add 10 more entries ... 158 for (int i = 0; i != 10; ++i) { 159 print("Adding entry " + value); 160 ringBuffer.add(value++); 161 // Thread.sleep(100L); 162 } 163 164 // Add a second consumer that should start seeing items 20 and up ... 165 MonotonicallyIncreasingConsumer consumer2 = new MonotonicallyIncreasingConsumer("second", 20L, 20L, 0); 166 ringBuffer.addConsumer(consumer2); 167 168 // Add 10 more entries ... 169 for (int i = 0; i != 10; ++i) { 170 print("Adding entry " + value); 171 ringBuffer.add(value++); 172 // Thread.sleep(100L); 173 } 174 175 ringBuffer.remove(consumer2); 176 177 // Add 10 more entries ... 178 for (int i = 0; i != 10; ++i) { 179 print("Adding entry " + value); 180 ringBuffer.add(value++); 181 // Thread.sleep(100L); 182 } 183 184 assertTrue(consumer2.isClosed()); 185 ringBuffer.shutdown(); 186 print(""); 187 print("Ring buffer shutdown completed"); 188 assertTrue(consumer1.isClosed()); 189 assertTrue(consumer2.isClosed()); 190 } 191 192 @Test 193 // @Ignore( "Takes a long time to run" ) 194 public void consumersShouldSeeEventsInCorrectOrder() throws Exception { 195 Executor executor = Executors.newCachedThreadPool(); 196 RingBuffer<Long, MonotonicallyIncreasingConsumer> ringBuffer = RingBufferBuilder.withSingleProducer(executor, 197 LongConsumerAdapter.INSTANCE) 198 .ofSize(8).garbageCollect(false).build(); 199 print = false; 200 201 // Add 10 entries with no consumers ... 202 long value = 0L; 203 for (int i = 0; i != 10; ++i) { 204 print("Adding entry " + value); 205 ringBuffer.add(value++); 206 } 207 208 // Add a single consumer that should start seeing items 10 and up ... 209 MonotonicallyIncreasingConsumer consumer1 = new MonotonicallyIncreasingConsumer("first", 10L, 10L, 0); 210 ringBuffer.addConsumer(consumer1); 211 212 // Add 10 more entries ... 213 for (int i = 0; i != 10; ++i) { 214 print("Adding entry " + value); 215 ringBuffer.add(value++); 216 // Thread.sleep(100L); 217 } 218 219 // Add a single consumer that should start seeing items 10 and up ... 220 MonotonicallyIncreasingConsumer consumer2 = new MonotonicallyIncreasingConsumer("second", 20L, 20L, 0); 221 ringBuffer.addConsumer(consumer2); 222 223 // Add 10 more entries ... 224 for (int i = 0; i != 10; ++i) { 225 print("Adding entry " + value); 226 ringBuffer.add(value++); 227 // Thread.sleep(100L); 228 } 229 230 // Add a second consumer that should start seeing items 20 and up ... 231 MonotonicallyIncreasingConsumer consumer3 = new MonotonicallyIncreasingConsumer("third", 30L, 30L, 0); 232 ringBuffer.addConsumer(consumer3); 233 234 // Add 10 more entries ... 235 for (int i = 0; i != 10; ++i) { 236 print("Adding entry " + value); 237 ringBuffer.add(value++); 238 // Thread.sleep(100L); 239 } 240 241 // Add a second consumer that should start seeing items 20 and up ... 242 MonotonicallyIncreasingConsumer consumer4 = new MonotonicallyIncreasingConsumer("fourth", 40L, 40L, 0); 243 ringBuffer.addConsumer(consumer4); 244 245 // Add 10 more entries ... 246 for (int i = 0; i != 10; ++i) { 247 print("Adding entry " + value); 248 ringBuffer.add(value++); 249 // Thread.sleep(100L); 250 } 251 252 // print = true; 253 slightPausesInConsumers = false; 254 boolean slightPauseBetweenEvents = false; 255 256 // Add 400K more entries 257 Stopwatch sw = new Stopwatch(); 258 int count = 2000; 259 sw.start(); 260 for (int i = 0; i != count; ++i) { 261 ringBuffer.add(value++); 262 if (slightPauseBetweenEvents) { 263 Thread.sleep(RANDOM.nextInt(50)); 264 } 265 } 266 sw.stop(); 267 268 // Do 10 more while printing ... 269 for (int i = 0; i != 10; ++i) { 270 // print = true; 271 print("Adding entry " + value); 272 ringBuffer.add(value++); 273 } 274 275 ringBuffer.shutdown(); 276 print(""); 277 print("Ring buffer shutdown completed"); 278 assertTrue(consumer1.isClosed()); 279 assertTrue(consumer2.isClosed()); 280 assertTrue(consumer3.isClosed()); 281 assertTrue(consumer4.isClosed()); 282 283 --value; 284 assertThat(consumer1.getLastValue(), is(value)); 285 assertThat(consumer2.getLastValue(), is(value)); 286 assertThat(consumer3.getLastValue(), is(value)); 287 assertThat(consumer4.getLastValue(), is(value)); 288 289 print(""); 290 print("Time to add " + count + " entries: " + sw.getAverageDuration()); 291 } 292 293 @Test 294 @FixFor( "MODE-2195" ) 295 public void shouldAutomaticallySetTheBufferSizeToTheNextPowerOf2() throws Exception { 296 Executor executor = Executors.newCachedThreadPool(); 297 RingBuffer<Long, Consumer<Long>> ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(5) 298 .garbageCollect(false).build(); 299 assertEquals(8, ringBuffer.getBufferSize()); 300 301 ringBuffer = RingBufferBuilder.withSingleProducer(executor, Long.class).ofSize(1023).garbageCollect(false).build(); 302 assertEquals(1024, ringBuffer.getBufferSize()); 303 } 304 305 protected void print( String message ) { 306 if (print) System.out.println(message); 307 } 308 309 protected class MonotonicallyIncreasingConsumer extends Consumer<Long> { 310 private final String id; 311 private boolean first = true; 312 private long lastValue = -1L; 313 private long lastPosition = -1L; 314 private boolean closed = false; 315 private final int secondsToWork; 316 317 public MonotonicallyIncreasingConsumer( String id, 318 long firstValue, 319 long firstPosition, 320 int secondsToWork ) { 321 this.id = id; 322 this.lastValue = firstValue; 323 this.lastPosition = firstPosition; 324 this.secondsToWork = secondsToWork; 325 } 326 327 @Override 328 public boolean consume( Long entry, 329 long position, 330 long max ) { 331 assertTrue(!closed); 332 print(id + " consuming " + entry.longValue() + " at position " + position + " with max " + max); 333 try { 334 Thread.sleep(secondsToWork * 1000); 335 } catch (Exception e) { 336 e.printStackTrace(); 337 fail(e.getMessage()); 338 } 339 if (slightPausesInConsumers && position % 1000 == 0) { 340 try { 341 Thread.sleep(RANDOM.nextInt(100)); 342 } catch (Exception e) { 343 e.printStackTrace(); 344 fail(e.getMessage()); 345 } 346 } 347 if (first) { 348 assertTrue(entry.longValue() == lastValue); 349 assertTrue(position == lastPosition); 350 first = false; 351 } else { 352 assertTrue(entry.longValue() == (lastValue + 1)); 353 lastValue = entry.longValue(); 354 assertTrue(position == (lastPosition + 1)); 355 lastPosition = position; 356 } 357 return true; 358 } 359 360 @Override 361 public void close() { 362 super.close(); 363 print(id + " closing"); 364 closed = true; 365 } 366 367 public long getLastPosition() { 368 return lastPosition; 369 } 370 371 public long getLastValue() { 372 return lastValue; 373 } 374 375 public boolean isClosed() { 376 return closed; 377 } 378 } 379 380 private static class LongConsumerAdapter implements RingBuffer.ConsumerAdapter<Long, MonotonicallyIncreasingConsumer> { 381 protected static final LongConsumerAdapter INSTANCE = new LongConsumerAdapter(); 382 383 private LongConsumerAdapter() { 384 } 385 386 @Override 387 public boolean consume( MonotonicallyIncreasingConsumer consumer, 388 Long event, 389 long position, 390 long maxPosition ) { 391 consumer.consume(event, position, maxPosition); 392 return true; 393 } 394 395 @Override 396 public void close( MonotonicallyIncreasingConsumer consumer ) { 397 consumer.close(); 398 } 399 400 @Override 401 public void handleException( MonotonicallyIncreasingConsumer consumer, 402 Throwable t, 403 Long entry, 404 long position, 405 long maxPosition ) { 406 throw new AssertionError("Test failure", t); 407 } 408 } 409 410}