/* * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. * * 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 reactor.core.publisher; import java.time.Duration; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.hamcrest.CoreMatchers; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.Fuseable; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; import reactor.test.publisher.FluxOperatorTest; import reactor.test.subscriber.AssertSubscriber; import reactor.util.concurrent.QueueSupplier; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.lessThan; import static org.junit.Assert.*; import static reactor.core.scheduler.Schedulers.fromExecutor; import static reactor.core.scheduler.Schedulers.fromExecutorService; public class FluxPublishOnTest extends FluxOperatorTest<String, String> { @Override protected Scenario<String, String> defaultScenarioOptions(Scenario<String, String> defaultOptions) { return defaultOptions.prefetch(QueueSupplier.SMALL_BUFFER_SIZE) .fusionModeThreadBarrier(Fuseable.ASYNC) .fusionMode(Fuseable.ASYNC); } void assertRejected(StepVerifier.Step<String> step) { try { step.consumeErrorWith(e -> Assert.assertTrue(Exceptions.unwrap(e) instanceof RejectedExecutionException)) .verify(); } catch (Exception e) { assertTrue(Exceptions.unwrap(e) instanceof RejectedExecutionException); } } void assertNoRejected(StepVerifier.Step<String> step) { try { step .thenAwait() .consumeErrorWith(e -> Assert.assertTrue(Exceptions.unwrap(e) instanceof RejectedExecutionException)) .verify(Duration.ofMillis(1)); } catch (Exception e) { assertTrue(Exceptions.unwrap(e) instanceof RejectedExecutionException); } catch (AssertionError e){ if(!e.getMessage().contains("timed out")) { throw e; } } } @Override protected List<Scenario<String, String>> scenarios_operatorError() { return Arrays.asList( scenario(f -> f.publishOn(Schedulers.fromExecutor(d -> { throw exception(); }))) .producerError(new RejectedExecutionException("Scheduler unavailable")) .receiverDemand(0), scenario(f -> f.publishOn(new FailWorkerScheduler())) .producerEmpty(), scenario(f -> f.publishOn(new FailNullWorkerScheduler())) .producerEmpty(), scenario(f -> f.publishOn(new RejectingWorkerScheduler(false, false))) .verifier(this::assertNoRejected) .receiverDemand(0), scenario(f -> f.publishOn(new RejectingWorkerScheduler(true, false))) .verifier(this::assertRejected) .receiverDemand(0), scenario(f -> f.publishOn(new RejectingWorkerScheduler(true, true))) .verifier(this::assertNoRejected) .receiverDemand(0), scenario(f -> f.publishOn(new RejectingWorkerScheduler(false, true))) .verifier(this::assertNoRejected), scenario(f -> f.publishOn(new RejectingWorkerScheduler(false, false))) .verifier(this::assertNoRejected) .receiverDemand(0), scenario(f -> f.publishOn(new RejectingWorkerScheduler(false, true))) .verifier(this::assertNoRejected) .producerEmpty(), scenario(f -> f.publishOn(new RejectingWorkerScheduler(true, false))) .verifier(this::assertRejected) .receiverDemand(0), scenario(f -> f.publishOn(new RejectingWorkerScheduler(true, true))) .verifier(this::assertNoRejected) .receiverDemand(0) ); } @Override protected List<Scenario<String, String>> scenarios_operatorSuccess() { return Arrays.asList( scenario(f -> f.publishOn(Schedulers.immediate())), scenario(f -> f.publishOn(Schedulers.immediate(), false, 4)) .prefetch(4), scenario(f -> f.publishOn(Schedulers.immediate(), 1)) .prefetch(1), scenario(f -> f.publishOn(Schedulers.immediate(), Integer.MAX_VALUE)) .prefetch(Integer.MAX_VALUE) ); } public static ExecutorService exec; @BeforeClass public static void before() { exec = Executors.newSingleThreadExecutor(); } @AfterClass public static void after() { exec.shutdownNow(); } @Test(expected = IllegalArgumentException.class) public void failPrefetch() { Flux.range(1, 10) .publishOn(Schedulers.immediate(), -1); } @Test public void normal() { StepVerifier.create(Flux.range(1, 1_000_000) .hide() .publishOn(Schedulers.fromExecutorService(exec))) .expectNextCount(1_000_000) .verifyComplete(); } @Test public void normalBackpressured1() throws Exception { StepVerifier.create(Flux.range(1, 1_000) .hide() .publishOn(Schedulers.fromExecutorService(exec)), 0) .thenRequest(500) .expectNextCount(500) .thenRequest(500) .expectNextCount(500) .verifyComplete(); } @Test public void normalBackpressured() throws Exception { StepVerifier.create(Flux.range(1, 1_000_000) .hide() .publishOn(Schedulers.fromExecutorService(exec)), 0) .thenRequest(500_000) .expectNextCount(500_000) .thenRequest(500_000) .expectNextCount(500_000) .verifyComplete(); } @Test public void normalSyncFused() { StepVerifier.create(Flux.range(1, 1_000_000) .publishOn(Schedulers.fromExecutorService(exec))) .expectNextCount(1_000_000) .verifyComplete(); } @Test public void normalSyncFusedBackpressured() throws Exception { StepVerifier.create(Flux.range(1, 1_000_000) .publishOn(Schedulers.fromExecutorService(exec)), 0) .thenRequest(500_000) .expectNextCount(500_000) .thenRequest(500_000) .expectNextCount(500_000) .verifyComplete(); } @Test public void normalAsyncFused() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); UnicastProcessor<Integer> up = UnicastProcessor.create(new ConcurrentLinkedQueue<>()); for (int i = 0; i < 1_000_000; i++) { up.onNext(i); } up.onComplete(); up.publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValueCount(1_000_000) .assertNoError() .assertComplete(); } @Test public void normalAsyncFusedBackpressured() throws Exception { UnicastProcessor<Integer> up = UnicastProcessor.create(QueueSupplier.<Integer>unbounded(1024).get()); for (int i = 0; i < 1_000_000; i++) { up.onNext(0); } up.onComplete(); StepVerifier.create(up.publishOn(Schedulers.fromExecutorService(exec)), 0) .expectSubscription() .thenRequest(500_000) .expectNextCount(500_000) .thenRequest(500_000) .expectNextCount(500_000) .verifyComplete(); } @Test public void error() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.<Integer>error(new RuntimeException("forced failure")).publishOn(Schedulers.fromExecutorService( exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertNoValues() .assertError(RuntimeException.class) .assertErrorMessage("forced failure") .assertNotComplete(); } @Test public void empty() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.<Integer>empty().publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertNoValues() .assertNoError() .assertComplete(); } @Test public void errorDelayed() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux<Integer> err = Flux.error(new RuntimeException("forced " + "failure")); Flux.range(1, 1000) .concatWith(err) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValueCount(1000) .assertError(RuntimeException.class) .assertErrorMessage("forced failure") .assertNotComplete(); } @Test public void classicJust() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.just(1) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertNoError() .assertComplete(); } @Test public void classicJustBackpressured() throws Exception { AssertSubscriber<Integer> ts = AssertSubscriber.create(0); Flux.just(1) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); Thread.sleep(100); ts.assertNoValues() .assertNoError() .assertNotComplete(); ts.request(500); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertNoError() .assertComplete(); } @Test public void filtered() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.range(1, 2_000_000) .hide() .publishOn(Schedulers.fromExecutorService(exec)) .filter(v -> (v & 1) == 0) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValueCount(1_000_000) .assertNoError() .assertComplete(); } @Test public void filtered1() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.range(1, 2_000) .hide() .publishOn(Schedulers.fromExecutorService(exec)) .filter(v -> (v & 1) == 0) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValueCount(1000) .assertNoError() .assertComplete(); } @Test public void normalFilteredBackpressured() throws Exception { StepVerifier.create(Flux.range(1, 2_000_000) .hide() .publishOn(Schedulers.fromExecutorService(exec)) .filter(v -> (v & 1) == 0), 0) .thenRequest(500_000) .expectNextCount(250_000) .thenRequest(500_000) .expectNextCount(750_000) .verifyComplete(); } @Test public void normalFilteredBackpressured1() throws Exception { StepVerifier.create(Flux.range(1, 2_000) .hide() .publishOn(Schedulers.fromExecutorService(exec)) .filter(v -> (v & 1) == 0), 0) .thenRequest(500) .expectNextCount(250) .thenRequest(500) .expectNextCount(750) .verifyComplete(); } @Test public void callableEvaluatedTheRightTime() { AtomicInteger count = new AtomicInteger(); Mono<Integer> p = Mono.fromCallable(count::incrementAndGet) .publishOn(Schedulers.fromExecutorService(ForkJoinPool.commonPool())); Assert.assertEquals(0, count.get()); AssertSubscriber<Integer> ts = AssertSubscriber.create(); p.subscribe(ts); if (!ts.await(Duration.ofSeconds(5)) .isTerminated()) { ts.cancel(); Assert.fail("AssertSubscriber timed out"); } Assert.assertEquals(1, count.get()); } public void diamond() { DirectProcessor<Integer> sp = DirectProcessor.create(); AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux<Integer> fork1 = sp.map(d -> d) .publishOn(Schedulers.fromExecutorService(exec)); Flux<Integer> fork2 = sp.map(d -> d) .publishOn(Schedulers.fromExecutorService(exec)); ts.request(256); Flux.merge(fork1, fork2) .publishOn(Schedulers.fromExecutorService(ForkJoinPool.commonPool())) .subscribe(ts); Flux.range(0, 128) .hide() .publishOn(Schedulers.fromExecutorService(ForkJoinPool.commonPool())) .subscribe(sp); ts.await(Duration.ofSeconds(5)) .assertTerminated() .assertValueCount(256) .assertNoError() .assertComplete(); } @Test public void prefetchAmountOnly() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); ConcurrentLinkedQueue<Long> clq = new ConcurrentLinkedQueue<>(); Flux.range(1, 2) .hide() .doOnRequest(v -> { clq.offer(v); }) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(100)); ts.assertValues(1, 2) .assertNoError() .assertComplete(); int s = clq.size(); Assert.assertTrue("More requests?" + clq, s == 1 || s == 2 || s == 3); Assert.assertEquals((Long) (long) QueueSupplier.SMALL_BUFFER_SIZE, clq.poll()); } @Test public void boundedQueue() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.range(1, 100_000) .hide() .publishOn(Schedulers.fromExecutor(exec), 128) .subscribe(ts); ts.await(Duration.ofSeconds(1)); ts.assertValueCount(100_000) .assertNoError() .assertComplete(); } @Test public void boundedQueueFilter() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.range(1, 100_000) .hide() .publishOn(Schedulers.fromExecutor(exec), 128) .filter(v -> (v & 1) == 0) .subscribe(ts); ts.await(Duration.ofSeconds(1)); ts.assertValueCount(50_000) .assertNoError() .assertComplete(); } @Test public void withFlatMap() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.range(1, 100_000) .flatMap(Flux::just) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValueCount(100_000) .assertNoError() .assertComplete(); } @Test public void syncSourceWithNull() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.just(1, null, 1) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void syncSourceWithNull2() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.fromIterable(Arrays.asList(1, null, 1)) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedsyncSourceWithNull() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.just(1, 2) .map(v -> v == 2 ? null : v) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedsyncSourceWithNullHidden() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.just(1, 2) .hide() .map(v -> v == 2 ? null : v) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedsyncSourceWithNullPostFilterHidden() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.just(1, 2) .hide() .map(v -> v == 2 ? null : v) .publishOn(Schedulers.fromExecutorService(exec)) .filter(v -> true) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedsyncSourceWithNull2() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.fromIterable(Arrays.asList(1, 2)) .map(v -> v == 2 ? null : v) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedsyncSourceWithNull2Hidden() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.fromIterable(Arrays.asList(1, 2)) .hide() .map(v -> v == 2 ? null : v) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedFilteredSyncSourceWithNull() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.just(1, 2) .map(v -> v == 2 ? null : v) .filter(v -> true) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedFilteredSyncSourceWithNull2() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Flux.fromIterable(Arrays.asList(1, 2)) .map(v -> v == 2 ? null : v) .filter(v -> true) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedAsyncSourceWithNull() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); UnicastProcessor<Integer> up = UnicastProcessor.create(QueueSupplier.<Integer>get(2).get()); up.onNext(1); up.onNext(2); up.onComplete(); up.map(v -> v == 2 ? null : v) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void mappedAsyncSourceWithNullPostFilter() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); UnicastProcessor<Integer> up = UnicastProcessor.create(QueueSupplier.<Integer>get(2).get()); up.onNext(1); up.onNext(2); up.onComplete(); up.map(v -> v == 2 ? null : v) .publishOn(Schedulers.fromExecutorService(exec)) .filter(v -> true) .subscribe(ts); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertError(NullPointerException.class) .assertNotComplete(); } @Test public void crossRangeHidden() { int count = 1000000; StepVerifier.create(Flux.range(1, count) .hide() .flatMap(v -> Flux.range(v, 2) .hide(), false, 128, 1) .hide() .publishOn(Schedulers.fromExecutorService(exec))) .expectNextCount(2 * count) .verifyComplete(); } @Test public void crossRange() { int count = 1000000; StepVerifier.create(Flux.range(1, count) .flatMap(v -> Flux.range(v, 2), false, 128, 1) .publishOn(Schedulers.fromExecutorService(exec))) .expectNextCount(2 * count) .verifyComplete(); } @Test public void crossRangeMaxHidden() throws Exception { int count = 1000000; StepVerifier.create(Flux.range(1, count) .hide() .flatMap(v -> Flux.range(v, 2) .hide(), false, 4, 32) .hide() .publishOn(Schedulers.fromExecutorService(exec))) .expectNextCount(2 * count) .verifyComplete(); } @Test public void crossRangeMax() { int count = 1000000; StepVerifier.create(Flux.range(1, count) .flatMap(v -> Flux.range(v, 2), false, 128, 32) .publishOn(Schedulers.fromExecutorService(exec))) .expectNextCount(2 * count) .verifyComplete(); } @Test public void crossRangeMaxUnbounded() { int count = 1000000; StepVerifier.create(Flux.range(1, count) .flatMap(v -> Flux.range(v, 2)) .publishOn(Schedulers.fromExecutorService(exec))) .expectNextCount(2 * count) .verifyComplete(); } @Test public void threadBoundaryPreventsInvalidFusionMap() { UnicastProcessor<Integer> up = UnicastProcessor.create(QueueSupplier.<Integer>get(2).get()); AssertSubscriber<String> ts = AssertSubscriber.create(); up.map(v -> Thread.currentThread() .getName()) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); up.onNext(1); up.onComplete(); ts.await(Duration.ofSeconds(5)); ts.assertValues(Thread.currentThread() .getName()) .assertNoError() .assertComplete(); } @Test public void threadBoundaryPreventsInvalidFusionFilter() { UnicastProcessor<Integer> up = UnicastProcessor.create(QueueSupplier.<Integer>get(2).get()); String s = Thread.currentThread() .getName(); AssertSubscriber<Integer> ts = AssertSubscriber.create(); up.filter(v -> s.equals(Thread.currentThread() .getName())) .publishOn(Schedulers.fromExecutorService(exec)) .subscribe(ts); up.onNext(1); up.onComplete(); ts.await(Duration.ofSeconds(5)); ts.assertValues(1) .assertNoError() .assertComplete(); } @Test public void crossRangePerfDefault() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); Scheduler scheduler = Schedulers.fromExecutorService(exec); int count = 1000; Flux<Integer> source = Flux.range(1, count) .flatMap(v -> Flux.range(v, 2), false, 128, 32); source.publishOn(scheduler) .subscribe(ts); if (!ts.await(Duration.ofSeconds(10)) .isTerminated()) { ts.cancel(); } ts.assertValueCount(count * 2) .assertNoError() .assertComplete(); } @Test public void limitRate() { List<Long> upstreamRequests = new LinkedList<>(); List<Long> downstreamRequests = new LinkedList<>(); Flux<Integer> source = Flux.range(1, 400) .doOnRequest(upstreamRequests::add) .doOnRequest(r -> System.out.println( "upstream request of " + r)) .limitRate(40) .doOnRequest(downstreamRequests::add) .doOnRequest(r -> System.out.println( "downstream request of " + r)); AssertSubscriber<Integer> ts = AssertSubscriber.create(400); source.subscribe(ts); ts.await(Duration.ofMillis(100)) .assertComplete(); Assert.assertThat("downstream didn't single request", downstreamRequests.size(), is(1)); Assert.assertThat("downstream didn't request 400", downstreamRequests.get(0), is(400L)); long total = 0L; for (Long requested : upstreamRequests) { total += requested; Assert.assertThat("rate limit not applied to request: " + requested, //30 is the optimization that eagerly prefetches when 3/4 of the request has been served requested, anyOf(is(40L), is(30L))); } Assert.assertThat("bad upstream total request", total, allOf(is(greaterThanOrEqualTo(400L)), is(lessThan(440L)))); } @Test(timeout = 5000) public void rejectedExecutionExceptionOnDataSignalExecutor() throws InterruptedException { final AtomicReference<Throwable> throwableInOnOperatorError = new AtomicReference<>(); final AtomicReference<Object> dataInOnOperatorError = new AtomicReference<>(); try { CountDownLatch hookLatch = new CountDownLatch(1); Hooks.onOperatorError((t, d) -> { throwableInOnOperatorError.set(t); dataInOnOperatorError.set(d); hookLatch.countDown(); return t; }); ExecutorService executor = newCachedThreadPool(); CountDownLatch latch = new CountDownLatch(1); AssertSubscriber<Integer> assertSubscriber = new AssertSubscriber<>(); Flux.range(0, 5) .publishOn(fromExecutorService(executor)) .doOnNext(s -> { try { latch.await(); } catch (InterruptedException e) { } }) .publishOn(fromExecutor(executor)) .subscribe(assertSubscriber); executor.shutdownNow(); assertSubscriber.assertNoValues() .assertNoError() .assertNotComplete(); hookLatch.await(); Assert.assertThat(throwableInOnOperatorError.get(), CoreMatchers.instanceOf(RejectedExecutionException.class)); Assert.assertSame(dataInOnOperatorError.get(), 0); } finally { Hooks.resetOnOperatorError(); } } @Test @Ignore //Fix or deprecate fromExecutor, this test might randomly hang on CI public void rejectedExecutionExceptionOnErrorSignalExecutor() throws InterruptedException { Exception exception = new IllegalStateException(); final AtomicReference<Throwable> throwableInOnOperatorError = new AtomicReference<>(); final AtomicReference<Object> dataInOnOperatorError = new AtomicReference<>(); try { CountDownLatch hookLatch = new CountDownLatch(2); Hooks.onOperatorError((t, d) -> { throwableInOnOperatorError.set(t); dataInOnOperatorError.set(d); hookLatch.countDown(); return t; }); ExecutorService executor = newCachedThreadPool(); CountDownLatch latch = new CountDownLatch(1); AssertSubscriber<Integer> assertSubscriber = new AssertSubscriber<>(); Flux.range(0, 5) .publishOn(fromExecutorService(executor)) .doOnNext(s -> { try { latch.await(); } catch (InterruptedException e) { throw Exceptions.propagate(exception); } }) .publishOn(fromExecutor(executor)) .subscribe(assertSubscriber); executor.shutdownNow(); assertSubscriber.assertNoValues() .assertNoError() .assertNotComplete(); hookLatch.await(); Assert.assertThat(throwableInOnOperatorError.get(), CoreMatchers.instanceOf(RejectedExecutionException.class)); Assert.assertSame(throwableInOnOperatorError.get() .getSuppressed()[0], exception); } finally { Hooks.resetOnOperatorError(); } } @Test(timeout = 5000) @Ignore public void rejectedExecutionExceptionOnDataSignalExecutorService() throws InterruptedException { CountDownLatch hookLatch = new CountDownLatch(1); Hooks.onOperatorError((t, d) -> { assertTrue(t instanceof RejectedExecutionException); assertTrue(d != null); hookLatch.countDown(); return t; }); try { ExecutorService executor = newCachedThreadPool(); StepVerifier.create(Flux.range(0, 5) .log() .publishOn(Schedulers.elastic()) .doOnRequest(n -> executor.shutdownNow()) .publishOn(fromExecutorService(executor)) .doOnNext(this::infiniteBlock)) .then(() -> { try { hookLatch.await(); } catch (InterruptedException e) { } }) .thenCancel() .verify(); } finally { Hooks.resetOnOperatorError(); } } void infiniteBlock(Integer t) { try { new CountDownLatch(1).await(); } catch (InterruptedException e) { } } @Test(timeout = 5000) public void rejectedExecutionExceptionOnErrorSignalExecutorService() throws InterruptedException { Exception exception = new IllegalStateException(); final AtomicReference<Throwable> throwableInOnOperatorError = new AtomicReference<>(); final AtomicReference<Object> dataInOnOperatorError = new AtomicReference<>(); try { CountDownLatch hookLatch = new CountDownLatch(2); Hooks.onOperatorError((t, d) -> { throwableInOnOperatorError.set(t); dataInOnOperatorError.set(d); hookLatch.countDown(); return t; }); ExecutorService executor = newCachedThreadPool(); CountDownLatch latch = new CountDownLatch(1); AssertSubscriber<Integer> assertSubscriber = new AssertSubscriber<>(); Flux.range(0, 5) .publishOn(fromExecutorService(executor)) .doOnNext(s -> { try { latch.await(); } catch (InterruptedException e) { throw Exceptions.propagate(exception); } }) .publishOn(fromExecutorService(executor)) .subscribe(assertSubscriber); executor.shutdownNow(); assertSubscriber.assertNoValues() .assertNoError() .assertNotComplete(); hookLatch.await(); Assert.assertThat(throwableInOnOperatorError.get(), CoreMatchers.instanceOf(RejectedExecutionException.class)); Assert.assertSame(throwableInOnOperatorError.get() .getSuppressed()[0], exception); } finally { Hooks.resetOnOperatorError(); } } /** * See #294 the consumer received more or less calls than expected Better reproducible * with big thread pools, e.g. 128 threads * * @throws InterruptedException on interrupt */ @Test public void mapNotifiesOnce() throws InterruptedException { final int COUNT = 10000; final Object internalLock = new Object(); final Object consumerLock = new Object(); final CountDownLatch internalLatch = new CountDownLatch(COUNT); final CountDownLatch counsumerLatch = new CountDownLatch(COUNT); final AtomicInteger internalCounter = new AtomicInteger(0); final AtomicInteger consumerCounter = new AtomicInteger(0); final ConcurrentHashMap<Object, Long> seenInternal = new ConcurrentHashMap<>(); final ConcurrentHashMap<Object, Long> seenConsumer = new ConcurrentHashMap<>(); EmitterProcessor<Integer> d = EmitterProcessor.create(); FluxSink<Integer> s = d.sink(); /*Disposable c = */ d.publishOn(Schedulers.parallel()) .parallel(8) .groups() .subscribe(stream -> stream.publishOn(Schedulers.parallel()) .map(o -> { synchronized (internalLock) { internalCounter.incrementAndGet(); long curThreadId = Thread.currentThread() .getId(); Long prevThreadId = seenInternal.put(o, curThreadId); if (prevThreadId != null) { fail(String.format( "The object %d has already been seen internally on the thread %d, current thread %d", o, prevThreadId, curThreadId)); } internalLatch.countDown(); } return -o; }) .subscribe(o -> { synchronized (consumerLock) { consumerCounter.incrementAndGet(); long curThreadId = Thread.currentThread() .getId(); Long prevThreadId = seenConsumer.put(o, curThreadId); if (prevThreadId != null) { System.out.println(String.format( "The object %d has already been seen by the consumer on the thread %d, current thread %d", o, prevThreadId, curThreadId)); fail(); } counsumerLatch.countDown(); } })); for (int i = 0; i < COUNT; i++) { s.next(i); } internalLatch.await(5, TimeUnit.SECONDS); assertEquals(COUNT, internalCounter.get()); counsumerLatch.await(5, TimeUnit.SECONDS); assertEquals(COUNT, consumerCounter.get()); } @Test public void mapManyFlushesAllValuesThoroughly() throws InterruptedException { int items = 1000; CountDownLatch latch = new CountDownLatch(items); Random random = ThreadLocalRandom.current(); EmitterProcessor<String> d = EmitterProcessor.create(); FluxSink<String> s = d.sink(); Flux<Integer> tasks = d.publishOn(Schedulers.parallel()) .parallel(8) .groups() .flatMap(stream -> stream.publishOn(Schedulers.parallel()) .map((String str) -> { try { Thread.sleep(random.nextInt( 10)); } catch (InterruptedException e) { Thread.currentThread() .interrupt(); } return Integer.parseInt(str); })); /* Disposable tail =*/ tasks.subscribe(i -> { latch.countDown(); }); for (int i = 1; i <= items; i++) { s.next(String.valueOf(i)); } latch.await(15, TimeUnit.SECONDS); assertTrue(latch.getCount() + " of " + items + " items were not counted down", latch.getCount() == 0); } @Test public void callablePath() { StepVerifier.create(Mono.fromCallable(() -> "test") .flux() .publishOn(Schedulers.immediate())) .expectNext("test") .verifyComplete(); StepVerifier.create(Mono.fromCallable(() -> { throw new Exception("test"); }) .flux() .publishOn(Schedulers.immediate())) .verifyErrorMessage("test"); StepVerifier.create(Mono.fromCallable(() -> null) .flux() .publishOn(Schedulers.immediate())) .verifyError(NullPointerException.class); } private static class FailNullWorkerScheduler implements Scheduler { @Override public Disposable schedule(Runnable task) { return Scheduler.REJECTED; } @Override public Worker createWorker() { return null; } } private static class RejectingWorkerScheduler implements Scheduler { final boolean isSchedulerTerminated; final boolean isWorkerTerminated; RejectingWorkerScheduler(boolean isSchedulerTerminated, boolean isWorkerTerminated) { this.isSchedulerTerminated = isSchedulerTerminated; this.isWorkerTerminated = isWorkerTerminated; } @Override public Disposable schedule(Runnable task) { return Scheduler.REJECTED; } @Override public boolean isDisposed() { return isSchedulerTerminated; } @Override public Worker createWorker() { return new Worker() { @Override public Disposable schedule(Runnable task) { return Scheduler.REJECTED; } @Override public void dispose() { } @Override public boolean isDisposed() { return isWorkerTerminated; } }; } } private class FailWorkerScheduler implements Scheduler { @Override public Disposable schedule(Runnable task) { return Scheduler.REJECTED; } @Override public Worker createWorker() { throw exception(); } } }