/** * Create a {@link Scheduler} which uses a backing {@link Executor} to schedule * Runnables for async operators. * * Trampolining here means tasks submitted in a burst are queued by the Worker itself, * which acts as a sole task from the perspective of the {@link ExecutorService}, * so no reordering (but also no threading). * * @param executor an {@link Executor} * @param trampoline set to false if this {@link Scheduler} is used by "operators" * that already conflate {@link Runnable} executions (publishOn, subscribeOn...) * * @return a new {@link Scheduler} */ public static Scheduler fromExecutor(Executor executor, boolean trampoline) { if(!trampoline && executor instanceof ExecutorService){ return fromExecutorService((ExecutorService) executor); } return new ExecutorScheduler(executor, trampoline); }
@Override public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); if (key == Attr.NAME) return toString(); return null; }
@Test public void failingExecutorRejects() { final IllegalStateException boom = new IllegalStateException("boom"); ExecutorScheduler scheduler = new ExecutorScheduler( task -> { throw boom;}, false ); AtomicBoolean done = new AtomicBoolean(); assertThatExceptionOfType(RejectedExecutionException.class) .isThrownBy(() -> scheduler.schedule(() -> done.set(true))) .withCause(boom); assertThat(done.get()).isFalse(); }
@Test public void failingPlainExecutorIsNotTerminated() { AtomicInteger count = new AtomicInteger(); final IllegalStateException boom = new IllegalStateException("boom"); ExecutorScheduler scheduler = new ExecutorScheduler( task -> { if (count.incrementAndGet() % 2 == 0) throw boom; }, false ); assertThatCode(() -> scheduler.schedule(() -> {})) .as("initial-no rejection") .doesNotThrowAnyException(); assertThatExceptionOfType(RejectedExecutionException.class) .as("second-transient rejection") .isThrownBy(() -> scheduler.schedule(() -> {})) .withCause(boom); assertThatCode(() -> scheduler.schedule(() -> {})) .as("third-no rejection") .doesNotThrowAnyException(); assertThat(count.get()).isEqualTo(3); }
ExecutorScheduler scheduler = new ExecutorScheduler(service, false); assertThatCode(() -> scheduler.schedule(() -> {})) .as("initial-no rejection") .doesNotThrowAnyException(); .isThrownBy(() -> scheduler.schedule(() -> {})) .withCause(boom); assertThatCode(() -> scheduler.schedule(() -> {})) .as("third-no rejection") .doesNotThrowAnyException();
@Override public Object scanUnsafe(Attr key) { if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); if (key == Attr.NAME) return toString(); return null; }
/** * Create a {@link Scheduler} which uses a backing {@link Executor} to schedule * Runnables for async operators. * * Trampolining here means tasks submitted in a burst are queued by the Worker itself, * which acts as a sole task from the perspective of the {@link ExecutorService}, * so no reordering (but also no threading). * * @param executor an {@link Executor} * @param trampoline set to false if this {@link Scheduler} is used by "operators" * that already conflate {@link Runnable} executions (publishOn, subscribeOn...) * * @return a new {@link Scheduler} */ public static Scheduler fromExecutor(Executor executor, boolean trampoline) { if(!trampoline && executor instanceof ExecutorService){ return fromExecutorService((ExecutorService) executor); } return new ExecutorScheduler(executor, trampoline); }
ExecutorScheduler scheduler = new ExecutorScheduler(service, false); assertThatCode(() -> scheduler.schedule(() -> {})) .as("initial-no rejection") .doesNotThrowAnyException(); .isThrownBy(() -> scheduler.schedule(() -> {})) .withCause(boom); .isThrownBy(() -> scheduler.schedule(() -> {})) .isSameAs(Exceptions.failWithRejected()) .withNoCause();