|
19 | 19 | import java.util.Arrays;
|
20 | 20 | import java.util.Formatter;
|
21 | 21 | import java.util.List;
|
| 22 | +import java.util.Objects; |
22 | 23 | import java.util.concurrent.BlockingQueue;
|
23 | 24 | import java.util.concurrent.ScheduledExecutorService;
|
24 | 25 | import java.util.concurrent.ScheduledThreadPoolExecutor;
|
25 | 26 | import java.util.concurrent.ThreadFactory;
|
26 | 27 | import java.util.concurrent.TimeUnit;
|
27 | 28 | import java.util.concurrent.atomic.AtomicInteger;
|
28 | 29 | import java.util.concurrent.atomic.AtomicLong;
|
| 30 | +import java.util.concurrent.locks.LockSupport; |
29 | 31 |
|
30 | 32 | import ch.qos.logback.core.status.OnConsoleStatusListener;
|
31 | 33 | import ch.qos.logback.core.status.Status;
|
| 34 | +import ch.qos.logback.core.util.Duration; |
| 35 | + |
32 | 36 | import net.logstash.logback.appender.listener.AppenderListener;
|
33 | 37 | import ch.qos.logback.access.spi.IAccessEvent;
|
34 | 38 | import ch.qos.logback.classic.AsyncAppender;
|
@@ -250,6 +254,41 @@ public abstract class AsyncDisruptorAppender<Event extends DeferredProcessingAwa
|
250 | 254 | */
|
251 | 255 | protected final List<Listener> listeners = new ArrayList<>();
|
252 | 256 |
|
| 257 | + public enum AsyncMode { |
| 258 | + /** |
| 259 | + * Appender thread is blocked until space is available in the ring buffer |
| 260 | + * or the retry timeout expires. |
| 261 | + */ |
| 262 | + BLOCK, |
| 263 | + |
| 264 | + /** |
| 265 | + * Event is dropped when the ring buffer is full |
| 266 | + */ |
| 267 | + DROP |
| 268 | + } |
| 269 | + private AsyncMode asyncMode = AsyncMode.DROP; |
| 270 | + |
| 271 | + /** |
| 272 | + * Delay (in millis) between consecutive attempts to append an event in the ring buffer when full. |
| 273 | + * Applicable only when {@link #asyncMode} is set to {@link AsyncMode#DROP}. |
| 274 | + */ |
| 275 | + private long retryMillis = 100; |
| 276 | + |
| 277 | + /** |
| 278 | + * Maximum time to wait for space in the ring buffer before dropping the event. |
| 279 | + * Applicable only when {@link #asyncMode} is set to {@link AsyncMode#DROP}. |
| 280 | + * |
| 281 | + * <p>Use {@code -1} for no timeout, i.e. block until space is available. |
| 282 | + */ |
| 283 | + private Duration retryTimeout = Duration.buildByMilliseconds(1000); |
| 284 | + |
| 285 | + /** |
| 286 | + * How long to wait for in-flight events during shutdown. |
| 287 | + */ |
| 288 | + private Duration shutdownGracePeriod = Duration.buildByMinutes(1); |
| 289 | + |
| 290 | + |
| 291 | + |
253 | 292 | /**
|
254 | 293 | * Event wrapper object used for each element of the {@link RingBuffer}.
|
255 | 294 | */
|
@@ -422,57 +461,141 @@ public void stop() {
|
422 | 461 | if (!super.isStarted()) {
|
423 | 462 | return;
|
424 | 463 | }
|
| 464 | + |
425 | 465 | /*
|
426 | 466 | * Don't allow any more events to be appended.
|
427 | 467 | */
|
428 | 468 | super.stop();
|
| 469 | + |
| 470 | + |
| 471 | + /* |
| 472 | + * Shutdown disruptor and executorService |
| 473 | + */ |
| 474 | + boolean errorDuringShutdown = false; |
| 475 | + long remainingTime = Math.max(0, getShutdownGracePeriod().getMilliseconds()); |
| 476 | + long startTime = System.currentTimeMillis(); |
| 477 | + |
429 | 478 | try {
|
430 |
| - this.disruptor.shutdown(1, TimeUnit.MINUTES); |
| 479 | + this.disruptor.shutdown(remainingTime, TimeUnit.MILLISECONDS); |
431 | 480 | } catch (TimeoutException e) {
|
432 |
| - addWarn("Some queued events have not been logged due to requested shutdown"); |
| 481 | + errorDuringShutdown = true; |
433 | 482 | }
|
434 | 483 |
|
435 | 484 | this.executorService.shutdown();
|
436 | 485 |
|
437 | 486 | try {
|
438 |
| - if (!this.executorService.awaitTermination(1, TimeUnit.MINUTES)) { |
439 |
| - addWarn("Some queued events have not been logged due to requested shutdown"); |
| 487 | + remainingTime = Math.max(0, remainingTime - (System.currentTimeMillis() - startTime)); |
| 488 | + if (!this.executorService.awaitTermination(remainingTime, TimeUnit.MILLISECONDS)) { |
| 489 | + errorDuringShutdown = true; |
440 | 490 | }
|
441 | 491 | } catch (InterruptedException e) {
|
442 |
| - addWarn("Some queued events have not been logged due to requested shutdown", e); |
| 492 | + errorDuringShutdown = true; |
443 | 493 | }
|
| 494 | + |
| 495 | + if (errorDuringShutdown) { |
| 496 | + addWarn("Some queued events have not been logged due to requested shutdown"); |
| 497 | + } |
| 498 | + |
| 499 | + |
| 500 | + /* |
| 501 | + * Notify listeners |
| 502 | + */ |
444 | 503 | fireAppenderStopped();
|
445 | 504 | }
|
446 | 505 |
|
| 506 | + |
447 | 507 | @Override
|
448 | 508 | protected void append(Event event) {
|
449 | 509 | long startTime = System.nanoTime();
|
| 510 | + |
450 | 511 | try {
|
451 | 512 | prepareForDeferredProcessing(event);
|
452 | 513 | } catch (RuntimeException e) {
|
453 |
| - addWarn("Unable to prepare event for deferred processing. Event output might be missing data.", e); |
| 514 | + addWarn("Unable to prepare event for deferred processing. Event output might be missing data.", e); |
454 | 515 | }
|
455 | 516 |
|
456 |
| - if (!this.disruptor.getRingBuffer().tryPublishEvent(this.eventTranslator, event)) { |
457 |
| - long consecutiveDropped = this.consecutiveDroppedCount.incrementAndGet(); |
458 |
| - if ((consecutiveDropped) % this.droppedWarnFrequency == 1) { |
459 |
| - addWarn("Dropped " + consecutiveDropped + " events (and counting...) due to ring buffer at max capacity [" + this.ringBufferSize + "]"); |
460 |
| - } |
461 |
| - fireEventAppendFailed(event, RING_BUFFER_FULL_EXCEPTION); |
462 |
| - } else { |
463 |
| - long endTime = System.nanoTime(); |
| 517 | + if (enqueueEvent(event)) { |
| 518 | + // Enqueue success - notify if we had errors previously |
| 519 | + // |
464 | 520 | long consecutiveDropped = this.consecutiveDroppedCount.get();
|
465 | 521 | if (consecutiveDropped != 0 && this.consecutiveDroppedCount.compareAndSet(consecutiveDropped, 0L)) {
|
466 | 522 | addWarn("Dropped " + consecutiveDropped + " total events due to ring buffer at max capacity [" + this.ringBufferSize + "]");
|
467 | 523 | }
|
468 |
| - fireEventAppended(event, endTime - startTime); |
| 524 | + |
| 525 | + // Notify parties |
| 526 | + // |
| 527 | + fireEventAppended(event, System.nanoTime() - startTime); |
| 528 | + |
| 529 | + } else { |
| 530 | + // Log a warning status about the failure |
| 531 | + // |
| 532 | + long consecutiveDropped = this.consecutiveDroppedCount.incrementAndGet(); |
| 533 | + if ((consecutiveDropped % this.droppedWarnFrequency) == 1) { |
| 534 | + addWarn("Dropped " + consecutiveDropped + " events (and counting...) due to ring buffer at max capacity [" + this.ringBufferSize + "]"); |
| 535 | + } |
| 536 | + |
| 537 | + // Notify parties |
| 538 | + // |
| 539 | + fireEventAppendFailed(event, RING_BUFFER_FULL_EXCEPTION); |
469 | 540 | }
|
470 | 541 | }
|
471 | 542 |
|
472 | 543 | protected void prepareForDeferredProcessing(Event event) {
|
473 | 544 | event.prepareForDeferredProcessing();
|
474 | 545 | }
|
475 | 546 |
|
| 547 | + /** |
| 548 | + * Enqueue the given {@code event} in the ring buffer according to the configured {@link #asyncMode}. |
| 549 | + * |
| 550 | + * @param event the {@link Event} to enqueue |
| 551 | + * @return {@code true} when the even is successfully enqueued in the ring buffer |
| 552 | + */ |
| 553 | + protected boolean enqueueEvent(Event event) { |
| 554 | + if (this.asyncMode == AsyncMode.BLOCK) { |
| 555 | + return enqueueEventBlock(event); |
| 556 | + } else { |
| 557 | + return enqueueEventDrop(event); |
| 558 | + } |
| 559 | + } |
| 560 | + |
| 561 | + /** |
| 562 | + * Enqueue the given {@code event} in the ring buffer, blocking until enough space |
| 563 | + * is available or the {@link #retryTimeout} expires (if configured). |
| 564 | + * |
| 565 | + * @param event the {@link Event} to enqueue |
| 566 | + * @return {@code true} when the even is successfully enqueued in the ring buffer |
| 567 | + */ |
| 568 | + private boolean enqueueEventBlock(Event event) { |
| 569 | + long timeout = this.retryTimeout.getMilliseconds() <= 0 ? Long.MAX_VALUE : System.currentTimeMillis() + this.retryTimeout.getMilliseconds(); |
| 570 | + |
| 571 | + while (isStarted() && !this.disruptor.getRingBuffer().tryPublishEvent(this.eventTranslator, event)) { |
| 572 | + // Check for timeout |
| 573 | + // |
| 574 | + if (System.currentTimeMillis() >= timeout) { |
| 575 | + return false; |
| 576 | + } |
| 577 | + |
| 578 | + // Wait before retry |
| 579 | + // |
| 580 | + long waitDuration = Math.min(this.retryMillis, System.currentTimeMillis() - timeout); |
| 581 | + if (waitDuration > 0) { |
| 582 | + LockSupport.parkNanos(waitDuration * 1_000_000L); |
| 583 | + } |
| 584 | + } |
| 585 | + |
| 586 | + return true; |
| 587 | + } |
| 588 | + |
| 589 | + /** |
| 590 | + * Attempt to enqueue the given {@code event} in the ring buffer without blocking. Drop the event |
| 591 | + * if the ring buffer is full. |
| 592 | + * |
| 593 | + * @param event the {@link Event} to enqueue |
| 594 | + * @return {@code true} when the even is successfully enqueued in the ring buffer |
| 595 | + */ |
| 596 | + private boolean enqueueEventDrop(Event event) { |
| 597 | + return this.disruptor.getRingBuffer().tryPublishEvent(this.eventTranslator, event); |
| 598 | + } |
476 | 599 |
|
477 | 600 | protected String calculateThreadName() {
|
478 | 601 | List<Object> threadNameFormatParams = getThreadNameFormatParams();
|
@@ -581,6 +704,34 @@ public void setWaitStrategyType(String waitStrategyType) {
|
581 | 704 | setWaitStrategy(WaitStrategyFactory.createWaitStrategyFromString(waitStrategyType));
|
582 | 705 | }
|
583 | 706 |
|
| 707 | + public AsyncMode getAsyncMode() { |
| 708 | + return asyncMode; |
| 709 | + } |
| 710 | + public void setAsyncMode(AsyncMode asyncMode) { |
| 711 | + this.asyncMode = asyncMode; |
| 712 | + } |
| 713 | + |
| 714 | + public long getRetryMillis() { |
| 715 | + return retryMillis; |
| 716 | + } |
| 717 | + public void setRetryMillis(long retryMillis) { |
| 718 | + this.retryMillis = retryMillis; |
| 719 | + } |
| 720 | + |
| 721 | + public Duration getRetryTimeout() { |
| 722 | + return retryTimeout; |
| 723 | + } |
| 724 | + public void setRetryTimeout(Duration retryTimeout) { |
| 725 | + this.retryTimeout = Objects.requireNonNull(retryTimeout); |
| 726 | + } |
| 727 | + |
| 728 | + public void setShutdownGracePeriod(Duration shutdownGracePeriod) { |
| 729 | + this.shutdownGracePeriod = Objects.requireNonNull(shutdownGracePeriod); |
| 730 | + } |
| 731 | + public Duration getShutdownGracePeriod() { |
| 732 | + return shutdownGracePeriod; |
| 733 | + } |
| 734 | + |
584 | 735 | public ThreadFactory getThreadFactory() {
|
585 | 736 | return threadFactory;
|
586 | 737 | }
|
|
0 commit comments