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 java.util.HashSet; 020import java.util.Set; 021import java.util.concurrent.CopyOnWriteArraySet; 022import java.util.concurrent.CountDownLatch; 023import java.util.concurrent.Executor; 024import java.util.concurrent.TimeUnit; 025import java.util.concurrent.TimeoutException; 026import java.util.concurrent.atomic.AtomicBoolean; 027import java.util.concurrent.locks.Condition; 028import java.util.concurrent.locks.Lock; 029import java.util.concurrent.locks.ReentrantLock; 030import org.modeshape.common.CommonI18n; 031import org.modeshape.common.collection.ring.GarbageCollectingConsumer.Collectable; 032import org.modeshape.common.logging.Logger; 033import org.modeshape.common.util.CheckArg; 034 035/** 036 * A circular or "ring" buffer that allows entries supplied by a producer to be easily, quickly, and independently consumed by 037 * multiple {@link Consumer consumers}. The design of this ring buffer attempts to eliminate or minimize contention between the 038 * different consumers. The ring buffer can be completely lock-free, although by default the consumers of the ring buffer use a 039 * {@link WaitStrategy} that blocks if they have processed all available entries and are waiting for more to be added. <h2> 040 * Concepts</h2> 041 * <p> 042 * Conceptually, this buffer consists of a fixed-sized ring of elements; entries are added at the ring's "cursor" while multiple 043 * consumers follow behind the cursor processing each of the entries as quickly as they can. Each consumer runs in its own thread, 044 * and work toward the cursor at their own pace, independently of all other consumers. Most importantly, every consumer sees the 045 * exact same order of entries. 046 * </p> 047 * <p> 048 * When the ring buffer starts out, it is empty and the cursor is at the starting position. As entries are added, the cursor 049 * travels around the ring, keeping track of its position and the position of all consumers. The cursor can never "lap" any of the 050 * consumers, and this ensures that the consumers see a consistent and ordered set of entries. Typically, consumers are fast 051 * enough that they trail relatively closely behind the cursor; plus, ring buffers are usually sized large enough so that the 052 * cursor rarely (if ever) closes on the slowest consumer. (If this does happen, consider increasing the size of the buffer or 053 * changing the consumers to process the entries more quickly, perhaps using a separate durable queue for those slow consumers.) 054 * </p> 055 * <h2>Consumers</h2> 056 * <p> 057 * Consumers can be added after the ring buffer has entries, but such consumers will only see those entries that are added after 058 * the consumer has been attached to the buffer. Additionally, the ring buffer guarantees that the consumers will be called from a 059 * single thread, so consumers do <em>not</em> need to be concurrent or thread-safe. 060 * </p> 061 * <h2>Batching</h2> 062 * <p> 063 * Even though there is almost no locking within the ring buffer, the ring buffer uses another technique to make it as fast as 064 * possible: batching. A producer can add multiple entries, called a "batch", at once. So rather than having to check for each 065 * entry the the values that are shared among the different threads, adding entries via a batch means the shared data needs to be 066 * checked only once per batch. 067 * </p> 068 * <p> 069 * The consumer threads also process batches, although most of this is hidden within the runnable that calls the 070 * {@link Consumer#consume(Object, long, long)} method. When ready to process an entry, this code asks for one entry and will get 071 * as many entries that are available. All of the returned entries can then be processed without having to check any of the shared 072 * data. 073 * </p> 074 * <h2>Shutdown</h2> 075 * <p> 076 * The {@link #shutdown()} method is a graceful termination that immediately prevents adding new entries and that allows all 077 * consumer threads to continue processing all previously-added entries. When each thread has consumed all entries, the consumer's 078 * thread will terminate and the consumer "unregistered" from the ring buffer. The method will block until all consumers have 079 * completed and are terminated. 080 * </p> 081 * <p> 082 * Once a ring buffer has been shutdown, it cannot be restarted. 083 * </p> 084 * 085 * @param <T> the type of entries stored in the buffer 086 * @param <C> the type of consumer 087 * @author Randall Hauch (rhauch@redhat.com) 088 */ 089public final class RingBuffer<T, C> { 090 091 private final int bufferSize; 092 private final int mask; 093 protected final Cursor cursor; 094 private final Object[] buffer; 095 private final Executor executor; 096 protected final AtomicBoolean addEntries = new AtomicBoolean(true); 097 protected final ConsumerAdapter<T, C> consumerAdapter; 098 private final Set<ConsumerRunner> consumers = new CopyOnWriteArraySet<>(); 099 private final GarbageCollectingConsumer gcConsumer; 100 private final Lock producerLock; 101 protected final Logger logger = Logger.getLogger(getClass()); 102 103 RingBuffer( String name, 104 Cursor cursor, 105 Executor executor, 106 ConsumerAdapter<T, C> consumerAdapter, 107 boolean gcEntries, 108 boolean singleProducer ) { 109 this.cursor = cursor; 110 this.bufferSize = cursor.getBufferSize(); 111 CheckArg.isPositive(bufferSize, "cursor.getBufferSize()"); 112 CheckArg.isPowerOfTwo(bufferSize, "cursor.getBufferSize()"); 113 this.mask = bufferSize - 1; 114 this.buffer = new Object[bufferSize]; 115 this.executor = executor; 116 this.consumerAdapter = consumerAdapter; 117 if (gcEntries) { 118 this.gcConsumer = this.cursor.createGarbageCollectingConsumer(new Collectable() { 119 120 @Override 121 public void collect( long position ) { 122 // System.out.println("---- CLEAR " + position); 123 clearEntry(position); 124 } 125 }); 126 this.executor.execute(gcConsumer); 127 } else { 128 this.gcConsumer = null; 129 } 130 131 if (singleProducer) { 132 // There is but one thread calling 'add', so no need for alock. Create an impl that does nothing ... 133 producerLock = new NoOpLock(); 134 } else { 135 // Multiple threads can call 'add', so use a real lock ... 136 producerLock = new ReentrantLock(); 137 } 138 } 139 140 /** 141 * Add to this buffer a single entry. This method blocks if there is no room in the ring buffer, providing back pressure on 142 * the caller in such cases. Note that if this method blocks for any length of time, that means at least one consumer has yet 143 * to process all of the entries that are currently in the ring buffer. In such cases, consider whether a larger ring buffer 144 * is warranted. 145 * 146 * @param entry the entry to be added; may not be null 147 * @return true if the entry was added, or false if the buffer has been {@link #shutdown()} 148 */ 149 public boolean add( T entry ) { 150 assert entry != null; 151 if (!addEntries.get()) return false; 152 try { 153 producerLock.lock(); 154 long position = cursor.claim(); // blocks; if this fails, we will not have successfully claimed and nothing to do ... 155 int index = (int)(position & mask); 156 buffer[index] = entry; 157 return cursor.publish(position); 158 } finally { 159 producerLock.unlock(); 160 } 161 } 162 163 /** 164 * Add to this buffer multiple entries. This method blocks until it is added. 165 * 166 * @param entries the entries that are to be added; may not be null 167 * @return true if all of the entries were added, or false if the buffer has been {@link #shutdown()} and none of the entries 168 * were added 169 */ 170 public boolean add( T[] entries ) { 171 assert entries != null; 172 if (entries.length == 0 || !addEntries.get()) return false; 173 try { 174 producerLock.lock(); 175 long position = cursor.claim(entries.length); // blocks 176 for (int i = 0; i != entries.length; ++i) { 177 int index = (int)(position & mask); 178 buffer[index] = entries[i]; 179 } 180 return cursor.publish(position); 181 } finally { 182 producerLock.unlock(); 183 } 184 } 185 186 @SuppressWarnings( "unchecked" ) 187 protected T getEntry( long position ) { 188 if (position < (cursor.getCurrent() - bufferSize)) { 189 // The cursor has already overwritten the entry ... 190 return null; 191 } 192 int index = (int)(position & mask); 193 return (T)buffer[index]; 194 } 195 196 protected void clearEntry( long position ) { 197 if (position < (cursor.getCurrent() - bufferSize)) { 198 // The cursor has already overwritten the entry ... 199 return; 200 } 201 int index = (int)(position & mask); 202 buffer[index] = null; 203 } 204 205 /** 206 * Add the supplied consumer, and have it start processing entries in a separate thread. 207 * <p> 208 * Note that the thread will block when there are no more entries to be consumed. If the thread gets a timeout when waiting 209 * for an entry, this method will retry the wait only one time before stopping. 210 * </p> 211 * <p> 212 * The consumer is automatically removed from the ring buffer when it returns {@code false} from its 213 * {@link Consumer#consume(Object, long, long)} method. 214 * </p> 215 * 216 * @param consumer the component that will process the entries; may not be null 217 * @return true if the consumer was added, or false if the consumer was already registered with this buffer 218 */ 219 public boolean addConsumer( final C consumer ) { 220 return addConsumer(consumer, 1); 221 } 222 223 /** 224 * Add the supplied consumer, and have it start processing entries in a separate thread. 225 * <p> 226 * The consumer is automatically removed from the ring buffer when it returns {@code false} from its 227 * {@link Consumer#consume(Object, long, long)} method. 228 * </p> 229 * 230 * @param consumer the component that will process the entries; may not be null 231 * @param timesToRetryUponTimeout the number of times that the thread should retry after timing out while waiting for the next 232 * entry; retries will not be attempted if the value is less than 1 233 * @return true if the consumer was added, or false if the consumer was already registered with this buffer 234 * @throws IllegalStateException if the ring buffer has already been {@link #shutdown()} 235 */ 236 public boolean addConsumer( final C consumer, 237 final int timesToRetryUponTimeout ) { 238 if (!addEntries.get()) { 239 throw new IllegalStateException(); 240 } 241 ConsumerRunner runner = new ConsumerRunner(consumer, timesToRetryUponTimeout); 242 if (gcConsumer != null) gcConsumer.stayBehind(runner.getPointer()); 243 244 // Try to add the runner instance, with equality based upon consumer instance equality ... 245 if (!consumers.add(runner)) return false; 246 247 // It was added, so start it ... 248 executor.execute(runner); 249 return true; 250 } 251 252 /** 253 * Remove the supplied consumer, and block until it stops running and is closed and removed from this buffer. The consumer is 254 * removed at the earliest conevenient point, and will stop seeing entries as soon as it is removed. 255 * 256 * @param consumer the consumer component to be removed entry; retries will not be attempted if the value is less than 1 257 * @return true if the consumer was removed, stopped, and closed, or false if the supplied consumer was not actually 258 * registered with this buffer (it may have completed) 259 * @throws IllegalStateException if the ring buffer has already been {@link #shutdown()} 260 */ 261 public boolean remove( C consumer ) { 262 if (consumer != null) { 263 // Iterate through the map to find the runner that owns this consumer ... 264 ConsumerRunner match = null; 265 for (ConsumerRunner runner : consumers) { 266 if (runner.getConsumer().equals(consumer)) { 267 match = runner; 268 break; 269 } 270 } 271 // Try to remove the matching runner (if we found one) from our list ... 272 if (match != null) { 273 // Tell the thread to stop and wait for it, after which it will have been removed from our map ... 274 match.close(); 275 return true; 276 } 277 } 278 // We either didn't find it, or we found it but something else remove it while we searched ... 279 return false; 280 } 281 282 /** 283 * Method called by the {@link ConsumerRunner#run()} method just before the method returns and the thread terminates. This 284 * method invocation allows this buffer to clean up its reference to the runner. 285 * 286 * @param runner the runner that has completed 287 */ 288 protected void disconnect( ConsumerRunner runner ) { 289 this.consumers.remove(runner); 290 if (gcConsumer != null) gcConsumer.ignore(runner.getPointer()); 291 } 292 293 protected int getBufferSize() { 294 return bufferSize; 295 } 296 297 /** 298 * Checks if there are any consumers registered. 299 * 300 * @return {@code true} if this buffer has any consumers, {@code false} otherwise. 301 */ 302 public boolean hasConsumers() { 303 return !this.consumers.isEmpty(); 304 } 305 306 /** 307 * Shutdown this ring buffer by preventing any further entries, but allowing all existing entries to be processed by all 308 * consumers. 309 */ 310 public void shutdown() { 311 // Prevent new entries from being added ... 312 this.addEntries.set(false); 313 314 // Mark the cursor as being finished; this will stop all consumers from waiting for a batch ... 315 this.cursor.complete(); 316 317 // Each of the consumer threads will complete the batch they're working on, but will then terminate ... 318 319 // Stop the garbage collection thread (if running) ... 320 if (this.gcConsumer != null) this.gcConsumer.close(); 321 322 // Now, block until all the runners have completed ... 323 for (ConsumerRunner runner : new HashSet<>(consumers)) { // use a copy of the runners; they're removed when they close 324 runner.waitForCompletion(); 325 } 326 assert consumers.isEmpty(); 327 } 328 329 /** 330 * Adapts the {@link #consume(Object, Object, long, long)}, {@link #close(Object)} and 331 * {@link #handleException(Object, Throwable, Object, long, long)} methods to other methods on an unknown type. 332 * 333 * @param <EntryType> the type of event 334 * @param <ConsumerType> the type of consumer 335 * @author Randall Hauch (rhauch@redhat.com) 336 */ 337 public static interface ConsumerAdapter<EntryType, ConsumerType> { 338 339 /** 340 * Consume an entry from the ring buffer. Generally all exceptions should be handled within this method; any exception 341 * thrown will result in the {@link #handleException(Object, Throwable, Object, long, long)} being called. 342 * 343 * @param consumer the consumer instance that is to consume the event; never null 344 * @param entry the entry; will not be null 345 * @param position the position of the entry within in the ring buffer; this is typically a monotonically-increasing value 346 * @param maxPosition the maximum position of entries in the ring buffer that are being consumed within the same batch; 347 * this will be greater or equal to {@code position} 348 * @return {@code true} if the consumer should continue processing the next entry, or {@code false} if this consumer is to 349 * stop processing any more entries (from this or subsequent batches); returning {@code false} provides a way for 350 * the consumer to signal that it should no longer be used 351 */ 352 boolean consume( ConsumerType consumer, 353 EntryType entry, 354 long position, 355 long maxPosition ); 356 357 /** 358 * Called by the {@link RingBuffer} when the {@link #consume(Object, Object, long, long)} method returns false, or when 359 * the buffer has been shutdown and the consumer has {@link #consume(Object, Object, long, long) consumed} all entries in 360 * the now-closed buffer. 361 * <p> 362 * This method allows any resources used by the consumer to be cleaned up when no longer needed 363 * </p> 364 * 365 * @param consumer the consumer instance that is being closed; never null 366 */ 367 void close( ConsumerType consumer ); 368 369 /** 370 * Handle an exception that was thrown from the {@link #consume(Object, Object, long, long)}. 371 * 372 * @param consumer the consumer instance that is to consume the event; never null 373 * @param t the exception; never null 374 * @param entry the entry during the consumption of which generated the exception; will not be null 375 * @param position the position of the entry within in the ring buffer; this is typically a monotonically-increasing value 376 * @param maxPosition the maximum position of entries in the ring buffer that are being consumed within the same batch; 377 * this will be greater or equal to {@code position} 378 */ 379 void handleException( ConsumerType consumer, 380 Throwable t, 381 EntryType entry, 382 long position, 383 long maxPosition ); 384 } 385 386 protected class ConsumerRunner implements Runnable { 387 private final C consumer; 388 private final PointerBarrier barrier; 389 private final Pointer pointer; 390 private final int timesToRetryUponTimeout; 391 private final AtomicBoolean runThread = new AtomicBoolean(true); 392 private final CountDownLatch stopLatch = new CountDownLatch(1); 393 394 protected ConsumerRunner( C consumer, 395 final int timesToRetryUponTimeout ) { 396 this.consumer = consumer; 397 this.timesToRetryUponTimeout = timesToRetryUponTimeout; 398 // Create a new barrier and a new pointer for consumer ... 399 this.barrier = cursor.newBarrier(); 400 this.pointer = cursor.newPointer(); // the cursor will not wrap beyond this pointer 401 } 402 403 protected Pointer getPointer() { 404 return pointer; 405 } 406 407 protected C getConsumer() { 408 return consumer; 409 } 410 411 @Override 412 public int hashCode() { 413 return consumer.hashCode(); 414 } 415 416 @Override 417 public boolean equals( Object obj ) { 418 if (this == obj) return true; 419 if (obj instanceof RingBuffer.ConsumerRunner) { 420 @SuppressWarnings( "unchecked" ) 421 ConsumerRunner that = (ConsumerRunner)obj; 422 return this.consumer.equals(that.consumer); 423 } 424 return false; 425 } 426 427 public void close() { 428 if (this.runThread.compareAndSet(true, false)) { 429 try { 430 this.barrier.close(); 431 // Need to wake up any dependent consumers/thread (e.g., garbage collection) ... 432 cursor.signalConsumers(); 433 this.stopLatch.await(); 434 } catch (InterruptedException e) { 435 // The thread was interrupted ... 436 Thread.interrupted(); 437 // do nothing ... 438 } 439 } 440 } 441 442 protected void waitForCompletion() { 443 try { 444 stopLatch.await(); 445 } catch (InterruptedException e) { 446 // The thread was interrupted ... 447 Thread.interrupted(); 448 // do nothing ... 449 } 450 } 451 452 @Override 453 public void run() { 454 boolean consume = true; 455 try { 456 int retry = timesToRetryUponTimeout; 457 while (consume && runThread.get()) { 458 T entry = null; 459 long next = pointer.get() + 1L; 460 try { 461 // Try to find the next position we can read to ... 462 long maxPosition = barrier.waitFor(next); 463 while (next <= maxPosition) { 464 entry = getEntry(next); 465 try { 466 if (!consumerAdapter.consume(consumer, entry, next, maxPosition)) { 467 // The consumer is done, so break out of the loop and clean up ... 468 consume = false; 469 break; 470 } 471 } catch (Throwable t) { 472 consumerAdapter.handleException(consumer, t, entry, next, maxPosition); 473 } 474 next = pointer.incrementAndGet() + 1L; 475 retry = timesToRetryUponTimeout; 476 } 477 if (maxPosition < 0) { 478 // The buffer has been shutdown and there are no more positions, so we're done ... 479 return; 480 } 481 } catch (TimeoutException e) { 482 // It took too long to wait, but keep trying ... 483 --retry; 484 if (retry < 0) { 485 return; 486 } 487 } catch (InterruptedException e) { 488 // The thread was interrupted ... 489 Thread.interrupted(); 490 break; 491 } catch (RuntimeException e) { 492 // Don't retry this entry, so just advance the pointer and continue ... 493 pointer.incrementAndGet(); 494 } 495 } 496 } finally { 497 // We are done ... 498 try { 499 consume = false; 500 // Tell the cursor to ignore our pointer ... 501 cursor.ignore(pointer); 502 } finally { 503 try { 504 consumerAdapter.close(consumer); 505 } catch (Throwable t) { 506 logger.error(t, CommonI18n.errorWhileClosingRingBufferConsumer, consumer, t.getMessage()); 507 } finally { 508 try { 509 disconnect(this); 510 } finally { 511 stopLatch.countDown(); 512 } 513 } 514 } 515 } 516 } 517 } 518 519 protected static final class NoOpLock implements Lock { 520 @Override 521 public void lock() { 522 } 523 524 @Override 525 public void unlock() { 526 } 527 528 @Override 529 public void lockInterruptibly() { 530 } 531 532 @Override 533 public boolean tryLock() { 534 return false; 535 } 536 537 @Override 538 public boolean tryLock( long time, 539 TimeUnit unit ) { 540 return false; 541 } 542 543 @Override 544 public Condition newCondition() { 545 return null; 546 } 547 } 548 549}