@Test(expected = NullPointerException.class) public void whenFactoryNull() { Flux.never() .retryWhen(null); }
@Test public void directOtherErrorPreventsSubscribe() { AtomicBoolean sourceSubscribed = new AtomicBoolean(); AtomicBoolean sourceCancelled = new AtomicBoolean(); Flux<Integer> source = justError .doOnSubscribe(sub -> sourceSubscribed.set(true)) .doOnCancel(() -> sourceCancelled.set(true)); Flux<Integer> retry = source.retryWhen(other -> Mono.error(new IllegalStateException("boom"))); StepVerifier.create(retry) .expectSubscription() .verifyErrorMessage("boom"); assertThat(sourceSubscribed.get()).isFalse(); assertThat(sourceCancelled.get()).isFalse(); }
@Test public void directOtherEmptyPreventsSubscribeAndCompletes() { AtomicBoolean sourceSubscribed = new AtomicBoolean(); AtomicBoolean sourceCancelled = new AtomicBoolean(); Flux<Integer> source = justError .doOnSubscribe(sub -> sourceSubscribed.set(true)) .doOnCancel(() -> sourceCancelled.set(true)); Flux<Integer> retry = source.retryWhen(other -> Flux.empty()); StepVerifier.create(retry) .expectSubscription() .verifyComplete(); assertThat(sourceSubscribed.get()).isFalse(); assertThat(sourceCancelled.get()).isFalse(); }
@Test public void cancelsOther() { AtomicBoolean cancelled = new AtomicBoolean(); Flux<Integer> when = Flux.range(1, 10) .doOnCancel(() -> cancelled.set(true)); StepVerifier.create(justError.retryWhen(other -> when)) .thenCancel() .verify(); assertThat(cancelled.get()).isTrue(); }
@Test public void cancelTwiceCancelsOtherOnce() { AtomicInteger cancelled = new AtomicInteger(); Flux<Integer> when = Flux.range(1, 10) .doOnCancel(cancelled::incrementAndGet); justError.retryWhen(other -> when) .subscribe(new BaseSubscriber<Integer>() { @Override protected void hookOnSubscribe(Subscription subscription) { subscription.request(1); subscription.cancel(); subscription.cancel(); } }); assertThat(cancelled.get()).isEqualTo(1); }
@Test public void lateOtherErrorCancelsSource() { AtomicBoolean sourceSubscribed = new AtomicBoolean(); AtomicBoolean sourceCancelled = new AtomicBoolean(); AtomicInteger count = new AtomicInteger(); Flux<Integer> source = justError .doOnSubscribe(sub -> sourceSubscribed.set(true)) .doOnCancel(() -> sourceCancelled.set(true)); Flux<Integer> retry = source.retryWhen(other -> other.flatMap(l -> count.getAndIncrement() == 0 ? Mono.just(l) : Mono.<Long>error(new IllegalStateException("boom")))); StepVerifier.create(retry) .expectSubscription() .expectNext(1) .expectNext(1) .verifyErrorMessage("boom"); assertThat(sourceSubscribed.get()).isTrue(); assertThat(sourceCancelled.get()).isTrue(); }
@Test public void lateOtherEmptyCancelsSourceAndCompletes() { AtomicBoolean sourceSubscribed = new AtomicBoolean(); AtomicBoolean sourceCancelled = new AtomicBoolean(); Flux<Integer> source = justError .doOnSubscribe(sub -> sourceSubscribed.set(true)) .doOnCancel(() -> sourceCancelled.set(true)); Flux<Integer> retry = source.retryWhen(other -> other.take(1)); StepVerifier.create(retry) .expectSubscription() .expectNext(1) //original .expectNext(1) //retry .verifyComplete(); //retry terminated assertThat(sourceSubscribed.get()).isTrue(); assertThat(sourceCancelled.get()).isTrue(); }
Flux<String> exponentialRetryScenario() { AtomicInteger i = new AtomicInteger(); return Flux.<String>create(s -> { if (i.incrementAndGet() == 4) { s.next("hey"); } else { s.error(new RuntimeException("test " + i)); } }).retryWhen(repeat -> repeat.zipWith(Flux.range(1, 3), (t1, t2) -> t2) .flatMap(time -> Mono.delay(Duration.ofSeconds(time)))); }
@Test public void retryErrorsInResponse() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); rangeError.retryWhen(v -> v.map(a -> { throw new RuntimeException("forced failure"); })) .subscribe(ts); ts.assertValues(1, 2) .assertError(RuntimeException.class) .assertErrorMessage("forced failure") .assertNotComplete(); }
@Test public void whenFactoryThrows() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); rangeError.retryWhen(v -> { throw new RuntimeException("forced failure"); }) .subscribe(ts); ts.assertNoValues() .assertError(RuntimeException.class) .assertErrorMessage("forced failure") .assertNotComplete(); }
@Test public void errorHandlingRetryWhenApproximateRetry() { Flux<String> flux = Flux.<String>error(new IllegalArgumentException()) // <1> .doOnError(System.out::println) // <2> .retryWhen(companion -> companion.take(3)); // <3> StepVerifier.create(flux) .verifyComplete(); StepVerifier.create(Flux.<String>error(new IllegalArgumentException()).retry(3)) .verifyError(); }
@Test public void whenFactoryReturnsNull() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); rangeError.retryWhen(v -> null) .subscribe(ts); ts.assertNoValues() .assertError(NullPointerException.class) .assertNotComplete(); }
.retryWhen(errorFlux -> errorFlux.flatMap(e -> Mono.subscriberContext().map(ctx -> Tuples.of(e, ctx))) .flatMap(t2 -> { Throwable e = t2.getT1();
@Test public void coldError() { AssertSubscriber<Integer> ts = AssertSubscriber.create(0); rangeError.retryWhen(v -> Flux.error(new RuntimeException("forced failure"))) .subscribe(ts); ts.assertNoValues() .assertError(RuntimeException.class) .assertErrorMessage("forced failure") .assertNotComplete(); }
@Test public void errorHandlingRetryWhenExponential() { Flux<String> flux = Flux.<String>error(new IllegalArgumentException()) .retryWhen(companion -> companion .doOnNext(s -> System.out.println(s + " at " + LocalTime.now())) // <1> .zipWith(Flux.range(1, 4), (error, index) -> { // <2> if (index < 4) return index; else throw Exceptions.propagate(error); }) .flatMap(index -> Mono.delay(Duration.ofMillis(index * 100))) // <3> .doOnNext(s -> System.out.println("retried at " + LocalTime.now())) // <4> ); StepVerifier.create(flux) .verifyError(IllegalArgumentException.class); }
@Test public void errorHandlingRetryWhenEquatesRetry() { Flux<String> flux = Flux.<String>error(new IllegalArgumentException()) .retryWhen(companion -> companion .zipWith(Flux.range(1, 4), (error, index) -> { // <1> if (index < 4) return index; // <2> else throw Exceptions.propagate(error); // <3> }) ); StepVerifier.create(flux) .verifyError(IllegalArgumentException.class); StepVerifier.create(Flux.<String>error(new IllegalArgumentException()).retry(3)) .verifyError(); }
@Test public void retryAlways() { AssertSubscriber<Integer> ts = AssertSubscriber.create(0); rangeError.retryWhen(v -> v) .subscribe(ts); ts.request(8); ts.assertValues(1, 2, 1, 2, 1, 2, 1, 2) .assertNoError() .assertNotComplete(); }
@Test public void coldRepeater() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); justError.retryWhen(v -> Flux.range(1, 10)) .subscribe(ts); ts.assertValues(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) .assertComplete() .assertNoError(); }
@Test public void coldEmpty() { AssertSubscriber<Integer> ts = AssertSubscriber.create(0); rangeError.retryWhen(v -> Flux.empty()) .subscribe(ts); ts.assertNoValues() .assertNoError() .assertComplete(); }
@Test public void coldRepeaterBackpressured() { AssertSubscriber<Integer> ts = AssertSubscriber.create(0); rangeError.retryWhen(v -> Flux.range(1, 5)) .subscribe(ts); ts.assertNoValues() .assertNoError() .assertNotComplete(); ts.request(1); ts.assertValues(1) .assertNoError() .assertNotComplete(); ts.request(2); ts.assertValues(1, 2, 1) .assertNoError() .assertNotComplete(); ts.request(5); ts.assertValues(1, 2, 1, 2, 1, 2, 1, 2) .assertNoError() .assertNotComplete(); ts.request(10); ts.assertValues(1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2) .assertComplete() .assertNoError(); }