From 473095cc2872451d304b87e21d990df93dc61c84 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Fri, 26 Jan 2024 01:54:43 +0900 Subject: [PATCH 1/9] Add `AutoCloseable` shourtcut on `Flux#using`, `Mono#using` Make `Flux, Mono#using` to close `AutoCloseable` resource, so that user don't have to pass resourceCleanup consumer. Fixes #3333. --- .../java/reactor/core/publisher/Flux.java | 37 ++++++++++++++++++- .../java/reactor/core/publisher/Mono.java | 24 +++++++++++- .../reactor/core/publisher/FluxUsingTest.java | 20 +++++++++- .../reactor/core/publisher/MonoUsingTest.java | 21 ++++++++++- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index b9a3213cb2..6ce74e6e84 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2024 VMware Inc. or its affiliates, 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. @@ -119,6 +119,7 @@ * @author Stephane Maldini * @author David Karnok * @author Simon Baslé + * @author Injae Kim * * @see Mono */ @@ -2100,6 +2101,32 @@ public static Flux using(Callable resourceSupplier, Funct return using(resourceSupplier, sourceSupplier, resourceCleanup, true); } + /** + * Uses a {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, + * while streaming the values from a Publisher derived from the same resource and makes sure + * the resource is released if the sequence terminates or the Subscriber cancels. + *

+ * Eager {@link AutoCloseable} resource cleanup happens just before the source termination and exceptions raised + * by the cleanup Consumer may override the terminal event. + *

+ * + *

+ * For an asynchronous version of the cleanup, with distinct path for onComplete, onError + * and cancel terminations, see {@link #usingWhen(Publisher, Function, Function, BiFunction, Function)}. + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource + * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource + * @param emitted type + * + * @return a new {@link Flux} built around a disposable resource + * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) + * @see #usingWhen(Publisher, Function, Function) + */ + public static Flux using(Callable resourceSupplier, + Function> sourceSupplier) { + return using(resourceSupplier, sourceSupplier, AUTO_CLOSE, true); + } + /** * Uses a resource, generated by a supplier for each individual Subscriber, while streaming the values from a * Publisher derived from the same resource and makes sure the resource is released if the sequence terminates or @@ -11110,6 +11137,14 @@ static Flux wrap(Publisher source) { return target; } + static final Consumer AUTO_CLOSE = resource -> { + try { + resource.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + @SuppressWarnings("rawtypes") static final BiFunction TUPLE2_BIFUNCTION = Tuples::of; @SuppressWarnings("rawtypes") diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index 083fe2e6dc..cab9e406fc 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2024 VMware Inc. or its affiliates, 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. @@ -115,6 +115,7 @@ * @author Stephane Maldini * @author David Karnok * @author Simon Baslé + * @author Injae Kim * @see Flux */ public abstract class Mono implements CorePublisher { @@ -912,6 +913,27 @@ public static Mono using(Callable resourceSupplier, return using(resourceSupplier, sourceSupplier, resourceCleanup, true); } + /** + * Uses a {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, + * while streaming the value from a Mono derived from the same resource and makes sure + * the resource is released if the sequence terminates or the Subscriber cancels. + *

+ * Unlike in {@link Flux#using(Callable, Function, Consumer) Flux}, in the case of a valued {@link Mono} the cleanup + * happens just before passing the value to downstream. In all cases, exceptions raised by the cleanup + * {@link Consumer} may override the terminal event, discarding the element if the derived {@link Mono} was valued. + *

+ * + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to create the resource + * @param sourceSupplier a {@link Mono} factory to create the Mono depending on the created resource + * @param emitted type + * + * @return new {@link Mono} + */ + public static Mono using(Callable resourceSupplier, + Function> sourceSupplier) { + return using(resourceSupplier, sourceSupplier, Flux.AUTO_CLOSE, true); + } /** * Uses a resource, generated by a {@link Publisher} for each individual {@link Subscriber}, diff --git a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java index 31e3e16b42..9519bf341d 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2024 VMware Inc. or its affiliates, 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. @@ -146,6 +146,24 @@ public void normalEager() { assertThat(cleanup).hasValue(1); } + @Test + public void normalAutoCloseable() { + AssertSubscriber ts = AssertSubscriber.create(); + + AtomicInteger cleanup = new AtomicInteger(); + + AutoCloseable resource = cleanup::incrementAndGet; + + Flux.using(() -> resource, r -> Flux.just(resource)) + .subscribe(ts); + + ts.assertValues(resource) + .assertComplete() + .assertNoError(); + + assertThat(cleanup).hasValue(1); + } + void checkCleanupExecutionTime(boolean eager, boolean fail) { AtomicInteger cleanup = new AtomicInteger(); AtomicBoolean before = new AtomicBoolean(); diff --git a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java index 20ba9d5a40..1152a32af8 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2024 VMware Inc. or its affiliates, 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. @@ -95,6 +95,25 @@ public void normalEager() { assertThat(cleanup).hasValue(1); } + @Test + public void normalAutoCloseable() { + AssertSubscriber ts = AssertSubscriber.create(); + + AtomicInteger cleanup = new AtomicInteger(); + + AutoCloseable resource = cleanup::incrementAndGet; + + Mono.using(() -> resource, r -> Mono.just(resource) + .doOnTerminate(() -> assertThat(cleanup).hasValue(0))) + .subscribe(ts); + + ts.assertValues(resource) + .assertComplete() + .assertNoError(); + + assertThat(cleanup).hasValue(1); + } + void checkCleanupExecutionTime(boolean eager, boolean fail) { AtomicInteger cleanup = new AtomicInteger(); AtomicBoolean before = new AtomicBoolean(); From 2cb4ea0ac35e87915c0baf15675986e3792a4e38 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Fri, 26 Jan 2024 18:30:04 +0900 Subject: [PATCH 2/9] Fix generic type and format --- reactor-core/src/main/java/reactor/core/publisher/Flux.java | 5 +++-- reactor-core/src/main/java/reactor/core/publisher/Mono.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index 6ce74e6e84..dc5ca1738b 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -2117,13 +2117,14 @@ public static Flux using(Callable resourceSupplier, Funct * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource * @param emitted type + * @param resource type * * @return a new {@link Flux} built around a disposable resource * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) * @see #usingWhen(Publisher, Function, Function) */ - public static Flux using(Callable resourceSupplier, - Function> sourceSupplier) { + public static Flux using(Callable resourceSupplier, + Function> sourceSupplier) { return using(resourceSupplier, sourceSupplier, AUTO_CLOSE, true); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index cab9e406fc..bbdbf55d87 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -927,11 +927,12 @@ public static Mono using(Callable resourceSupplier, * @param resourceSupplier a {@link Callable} that is called on subscribe to create the resource * @param sourceSupplier a {@link Mono} factory to create the Mono depending on the created resource * @param emitted type + * @param resource type * * @return new {@link Mono} */ - public static Mono using(Callable resourceSupplier, - Function> sourceSupplier) { + public static Mono using(Callable resourceSupplier, + Function> sourceSupplier) { return using(resourceSupplier, sourceSupplier, Flux.AUTO_CLOSE, true); } From 556172ef53d70d2fd2e0e26032bacf111ab923c6 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Wed, 31 Jan 2024 22:32:33 +0900 Subject: [PATCH 3/9] Address comments --- .../main/java/reactor/core/Exceptions.java | 16 ++++++- .../java/reactor/core/publisher/Flux.java | 45 ++++++++++++++----- .../java/reactor/core/publisher/Mono.java | 34 ++++++++++++-- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/Exceptions.java b/reactor-core/src/main/java/reactor/core/Exceptions.java index 8328ca05a6..79c18fd462 100644 --- a/reactor-core/src/main/java/reactor/core/Exceptions.java +++ b/reactor-core/src/main/java/reactor/core/Exceptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2016-2024 VMware Inc. or its affiliates, 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. @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; import reactor.core.publisher.Flux; import reactor.util.Logger; @@ -34,6 +35,7 @@ * Global Reactor Core Exception handling and utils to operate on. * * @author Stephane Maldini + * @author Injae Kim * @see Reactive-Streams-Commons */ public abstract class Exceptions { @@ -861,4 +863,16 @@ static final class StaticThrowable extends Error { } } + /** + * A general-purpose {@link Consumer} that closes {@link AutoCloseable} resource. + * If exception is thrown during closing the resource, it will be propagated by {@link Exceptions#propagate(Throwable)}. + */ + public static final Consumer AUTO_CLOSE = resource -> { + try { + resource.close(); + } catch (Throwable t) { + throw Exceptions.propagate(t); + } + }; + } diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index dc5ca1738b..5ebfb0c93e 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -2102,7 +2102,7 @@ public static Flux using(Callable resourceSupplier, Funct } /** - * Uses a {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, + * Uses an {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, * while streaming the values from a Publisher derived from the same resource and makes sure * the resource is released if the sequence terminates or the Subscriber cancels. *

@@ -2123,9 +2123,40 @@ public static Flux using(Callable resourceSupplier, Funct * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) * @see #usingWhen(Publisher, Function, Function) */ - public static Flux using(Callable resourceSupplier, + public static Flux using(Callable resourceSupplier, Function> sourceSupplier) { - return using(resourceSupplier, sourceSupplier, AUTO_CLOSE, true); + return using(resourceSupplier, sourceSupplier, true); + } + + /** + * Uses an {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, + * while streaming the values from a Publisher derived from the same resource and makes sure + * the resource is released if the sequence terminates or the Subscriber cancels. + *

+ *

    + *
  • Eager {@link AutoCloseable} resource cleanup happens just before the source termination and exceptions raised + * by the cleanup Consumer may override the terminal event.
  • + *
  • Non-eager cleanup will drop any exception.
  • + *
+ *

+ * + *

+ * For an asynchronous version of the cleanup, with distinct path for onComplete, onError + * and cancel terminations, see {@link #usingWhen(Publisher, Function, Function, BiFunction, Function)}. + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource + * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource + * @param eager true to clean before terminating downstream subscribers + * @param emitted type + * @param resource type + * + * @return a new {@link Flux} built around a disposable resource + * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) + * @see #usingWhen(Publisher, Function, Function) + */ + public static Flux using(Callable resourceSupplier, + Function> sourceSupplier, boolean eager) { + return using(resourceSupplier, sourceSupplier, Exceptions.AUTO_CLOSE, eager); } /** @@ -11138,14 +11169,6 @@ static Flux wrap(Publisher source) { return target; } - static final Consumer AUTO_CLOSE = resource -> { - try { - resource.close(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }; - @SuppressWarnings("rawtypes") static final BiFunction TUPLE2_BIFUNCTION = Tuples::of; @SuppressWarnings("rawtypes") diff --git a/reactor-core/src/main/java/reactor/core/publisher/Mono.java b/reactor-core/src/main/java/reactor/core/publisher/Mono.java index bbdbf55d87..847cf3fcaa 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Mono.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Mono.java @@ -914,7 +914,7 @@ public static Mono using(Callable resourceSupplier, } /** - * Uses a {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, + * Uses an {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, * while streaming the value from a Mono derived from the same resource and makes sure * the resource is released if the sequence terminates or the Subscriber cancels. *

@@ -931,9 +931,37 @@ public static Mono using(Callable resourceSupplier, * * @return new {@link Mono} */ - public static Mono using(Callable resourceSupplier, + public static Mono using(Callable resourceSupplier, Function> sourceSupplier) { - return using(resourceSupplier, sourceSupplier, Flux.AUTO_CLOSE, true); + return using(resourceSupplier, sourceSupplier, true); + } + + /** + * Uses an {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, + * while streaming the value from a Mono derived from the same resource and makes sure + * the resource is released if the sequence terminates or the Subscriber cancels. + *

+ *

    + *
  • For eager cleanup, Unlike in {@link Flux#using(Callable, Function, Consumer) Flux}, + * in the case of a valued {@link Mono} the cleanup happens just before passing the value to downstream. + * In all cases, exceptions raised by the cleanup {@link Consumer} may override the terminal event, + * discarding the element if the derived {@link Mono} was valued.
  • + *
  • Non-eager cleanup will drop any exception.
  • + *
+ *

+ * + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to create the resource + * @param sourceSupplier a {@link Mono} factory to create the Mono depending on the created resource + * @param eager set to true to clean before any signal (including onNext) is passed downstream + * @param emitted type + * @param resource type + * + * @return new {@link Mono} + */ + public static Mono using(Callable resourceSupplier, + Function> sourceSupplier, boolean eager) { + return using(resourceSupplier, sourceSupplier, Exceptions.AUTO_CLOSE, eager); } /** From eadd075fb04a713586a0e25ca5b080f421073fa2 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Thu, 1 Feb 2024 03:52:30 +0900 Subject: [PATCH 4/9] Add test cases with ParameterizedTest --- .../reactor/core/publisher/FluxUsingTest.java | 195 ++++++++++------- .../reactor/core/publisher/MonoUsingTest.java | 200 +++++++++++------- 2 files changed, 248 insertions(+), 147 deletions(-) diff --git a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java index 9519bf341d..0abc979325 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java @@ -23,7 +23,9 @@ import org.assertj.core.api.Assertions; import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; import org.reactivestreams.Subscription; @@ -31,6 +33,7 @@ import reactor.core.Fuseable; import reactor.core.Scannable; import reactor.test.MockUtils; +import reactor.test.ParameterizedTestWithName; import reactor.test.StepVerifier; import reactor.test.publisher.FluxOperatorTest; import reactor.test.subscriber.AssertSubscriber; @@ -41,6 +44,74 @@ public class FluxUsingTest extends FluxOperatorTest { + public static List> sourcesNonEager() { + return Arrays.asList( + Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set, false), + Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10), false) + ); + } + + public static List> sourcesEager() { + return Arrays.asList( + Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set), + Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set, true), + Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10)), + Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10), true) + ); + } + + public static List> sourcesFailNonEager() { + return Arrays.asList( + Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set, false), + Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure")), false) + ); + } + + public static List> sourcesFailEager() { + return Arrays.asList( + Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set), + Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set, true), + Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure"))), + Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure")), true) + ); + } + + public static List> resourcesThrow() { + return Arrays.asList( + // non eager + Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set, false), + Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10), false), + // eager + Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set), + Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set, true), + Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10)), + Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10), true) + ); + } + + public static List> sourcesThrowNonEager() { + return Arrays.asList( + Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, false), + Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, false) + ); + } + + public static List> sourcesThrowEager() { + return Arrays.asList( + Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set), + Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, true), + Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }), + Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, true) + ); + } + + private static final AtomicInteger cleanup = new AtomicInteger(); + + @BeforeEach + public void before() { + cleanup.set(0); + } + @Override protected Scenario defaultScenarioOptions(Scenario defaultOptions) { return defaultOptions.fusionMode(Fuseable.ANY) @@ -114,14 +185,12 @@ public void resourceCleanupNull() { }); } - @Test - public void normal() { + @ParameterizedTestWithName + @MethodSource("sourcesNonEager") + public void normal(Flux source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - - Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set, false) - .subscribe(ts); + source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertComplete() @@ -130,14 +199,14 @@ public void normal() { assertThat(cleanup).hasValue(1); } - @Test - public void normalEager() { + @ParameterizedTestWithName + @MethodSource("sourcesEager") + public void normalEager(Flux source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - - Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set) - .subscribe(ts); + source.doFinally(event -> assertThat(cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) + .subscribe(ts); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertComplete() @@ -146,26 +215,7 @@ public void normalEager() { assertThat(cleanup).hasValue(1); } - @Test - public void normalAutoCloseable() { - AssertSubscriber ts = AssertSubscriber.create(); - - AtomicInteger cleanup = new AtomicInteger(); - - AutoCloseable resource = cleanup::incrementAndGet; - - Flux.using(() -> resource, r -> Flux.just(resource)) - .subscribe(ts); - - ts.assertValues(resource) - .assertComplete() - .assertNoError(); - - assertThat(cleanup).hasValue(1); - } - - void checkCleanupExecutionTime(boolean eager, boolean fail) { - AtomicInteger cleanup = new AtomicInteger(); + void checkCleanupExecutionTime(Flux source, boolean eager, boolean fail) { AtomicBoolean before = new AtomicBoolean(); AssertSubscriber ts = new AssertSubscriber() { @@ -182,13 +232,7 @@ public void onComplete() { } }; - Flux.using(() -> 1, r -> { - if (fail) { - return Flux.error(new RuntimeException("forced failure")); - } - return Flux.range(r, 10); - }, cleanup::set, eager) - .subscribe(ts); + source.subscribe(ts); if (fail) { ts.assertNoValues() @@ -206,36 +250,36 @@ public void onComplete() { assertThat(before.get()).isEqualTo(eager); } - @Test - public void checkNonEager() { - checkCleanupExecutionTime(false, false); + @ParameterizedTestWithName + @MethodSource("sourcesNonEager") + public void checkNonEager(Flux source) { + checkCleanupExecutionTime(source, false, false); } - @Test - public void checkEager() { - checkCleanupExecutionTime(true, false); + @ParameterizedTestWithName + @MethodSource("sourcesEager") + public void checkEager(Flux source) { + checkCleanupExecutionTime(source, true, false); } - @Test - public void checkErrorNonEager() { - checkCleanupExecutionTime(false, true); + @ParameterizedTestWithName + @MethodSource("sourcesFailNonEager") + public void checkErrorNonEager(Flux source) { + checkCleanupExecutionTime(source, false, true); } - @Test - public void checkErrorEager() { - checkCleanupExecutionTime(true, true); + @ParameterizedTestWithName + @MethodSource("sourcesFailEager") + public void checkErrorEager(Flux source) { + checkCleanupExecutionTime(source, true, true); } - @Test - public void resourceThrowsEager() { + @ParameterizedTestWithName + @MethodSource("resourcesThrow") + public void resourceThrows(Flux source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - - Flux.using(() -> { - throw new RuntimeException("forced failure"); - }, r -> Flux.range(1, 10), cleanup::set, false) - .subscribe(ts); + source.subscribe(ts); ts.assertNoValues() .assertNotComplete() @@ -245,16 +289,29 @@ public void resourceThrowsEager() { assertThat(cleanup).hasValue(0); } - @Test - public void factoryThrowsEager() { + @ParameterizedTestWithName + @MethodSource("sourcesThrowNonEager") + public void factoryThrowsNonEager(Flux source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); + source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); - Flux.using(() -> 1, r -> { - throw new RuntimeException("forced failure"); - }, cleanup::set, false) - .subscribe(ts); + ts.assertNoValues() + .assertNotComplete() + .assertError(RuntimeException.class) + .assertErrorMessage("forced failure"); + + assertThat(cleanup).hasValue(1); + } + + @ParameterizedTestWithName + @MethodSource("sourcesThrowEager") + public void factoryThrowsEager(Flux source) { + AssertSubscriber ts = AssertSubscriber.create(); + + source.doFinally(event -> assertThat(cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) + .subscribe(ts); ts.assertNoValues() .assertNotComplete() @@ -268,8 +325,6 @@ public void factoryThrowsEager() { public void factoryReturnsNull() { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - Flux.using(() -> 1, r -> null, cleanup::set, @@ -286,8 +341,6 @@ public void factoryReturnsNull() { public void subscriberCancels() { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - Sinks.Many tp = Sinks.unsafe().many().multicast().directBestEffort(); Flux.using(() -> 1, r -> tp.asFlux(), cleanup::set, true) @@ -339,8 +392,6 @@ public void sourceFactoryAndResourceCleanupThrow() { @Test public void scanOperator(){ - AtomicInteger cleanup = new AtomicInteger(); - FluxUsing test = new FluxUsing<>(() -> 1, r -> Flux.range(r, 10), cleanup::set, false); assertThat(test.scan(Scannable.Attr.RUN_STYLE)).isSameAs(Scannable.Attr.RunStyle.SYNC); diff --git a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java index 1152a32af8..26ea51faa5 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java @@ -17,16 +17,21 @@ package reactor.core.publisher; import java.time.Duration; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.MethodSource; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Scannable; +import reactor.test.ParameterizedTestWithName; import reactor.test.StepVerifier; import reactor.test.subscriber.AssertSubscriber; @@ -36,6 +41,74 @@ public class MonoUsingTest { + public static List> sourcesNonEager() { + return Arrays.asList( + Mono.using(() -> 1, Mono::just, cleanup::set, false), + Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1), false) + ); + } + + public static List> sourcesEager() { + return Arrays.asList( + Mono.using(() -> 1, Mono::just, cleanup::set), + Mono.using(() -> 1, Mono::just, cleanup::set, true), + Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1)), + Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1), true) + ); + } + + public static List> sourcesFailNonEager() { + return Arrays.asList( + Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set, false), + Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure")), false) + ); + } + + public static List> sourcesFailEager() { + return Arrays.asList( + Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set), + Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set, true), + Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure"))), + Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure")), true) + ); + } + + public static List> resourcesThrow() { + return Arrays.asList( + // non eager + Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set, false), + Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1), false), + // eager + Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set), + Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set, true), + Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1)), + Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1), true) + ); + } + + public static List> sourcesThrowNonEager() { + return Arrays.asList( + Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, false), + Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, false) + ); + } + + public static List> sourcesThrowEager() { + return Arrays.asList( + Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set), + Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, true), + Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }), + Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, true) + ); + } + + private static final AtomicInteger cleanup = new AtomicInteger(); + + @BeforeEach + public void before() { + cleanup.set(0); + } + @Test public void resourceSupplierNull() { assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> { @@ -59,15 +132,12 @@ public void resourceCleanupNull() { }); } - @Test - public void normal() { + @ParameterizedTestWithName + @MethodSource("sourcesNonEager") + public void normal(Mono source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - - Mono.using(() -> 1, r -> Mono.just(1), cleanup::set, false) - .doAfterTerminate(() -> assertThat(cleanup).hasValue(0)) - .subscribe(ts); + source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); ts.assertValues(1) .assertComplete() @@ -76,17 +146,14 @@ public void normal() { assertThat(cleanup).hasValue(1); } - @Test - public void normalEager() { + @ParameterizedTestWithName + @MethodSource("sourcesEager") + public void normalEager(Mono source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - - Mono.using(() -> 1, r -> Mono.just(1) - .doOnTerminate(() -> assertThat(cleanup).hasValue(0)), - cleanup::set, - true) - .subscribe(ts); + source.doFinally(event -> assertThat(cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) + .subscribe(ts); ts.assertValues(1) .assertComplete() @@ -95,27 +162,7 @@ public void normalEager() { assertThat(cleanup).hasValue(1); } - @Test - public void normalAutoCloseable() { - AssertSubscriber ts = AssertSubscriber.create(); - - AtomicInteger cleanup = new AtomicInteger(); - - AutoCloseable resource = cleanup::incrementAndGet; - - Mono.using(() -> resource, r -> Mono.just(resource) - .doOnTerminate(() -> assertThat(cleanup).hasValue(0))) - .subscribe(ts); - - ts.assertValues(resource) - .assertComplete() - .assertNoError(); - - assertThat(cleanup).hasValue(1); - } - - void checkCleanupExecutionTime(boolean eager, boolean fail) { - AtomicInteger cleanup = new AtomicInteger(); + void checkCleanupExecutionTime(Mono source, boolean eager, boolean fail) { AtomicBoolean before = new AtomicBoolean(); AssertSubscriber ts = new AssertSubscriber() { @@ -132,13 +179,7 @@ public void onComplete() { } }; - Mono.using(() -> 1, r -> { - if (fail) { - return Mono.error(new RuntimeException("forced failure")); - } - return Mono.just(1); - }, cleanup::set, eager) - .subscribe(ts); + source.subscribe(ts); if (fail) { ts.assertNoValues() @@ -156,36 +197,36 @@ public void onComplete() { assertThat(before.get()).isEqualTo(eager); } - @Test - public void checkNonEager() { - checkCleanupExecutionTime(false, false); + @ParameterizedTestWithName + @MethodSource("sourcesNonEager") + public void checkNonEager(Mono source) { + checkCleanupExecutionTime(source, false, false); } - @Test - public void checkEager() { - checkCleanupExecutionTime(true, false); + @ParameterizedTestWithName + @MethodSource("sourcesEager") + public void checkEager(Mono source) { + checkCleanupExecutionTime(source, true, false); } - @Test - public void checkErrorNonEager() { - checkCleanupExecutionTime(false, true); + @ParameterizedTestWithName + @MethodSource("sourcesFailNonEager") + public void checkErrorNonEager(Mono source) { + checkCleanupExecutionTime(source, false, true); } - @Test - public void checkErrorEager() { - checkCleanupExecutionTime(true, true); + @ParameterizedTestWithName + @MethodSource("sourcesFailEager") + public void checkErrorEager(Mono source) { + checkCleanupExecutionTime(source, true, true); } - @Test - public void resourceThrowsEager() { + @ParameterizedTestWithName + @MethodSource("resourcesThrow") + public void resourceThrowsEager(Mono source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - - Mono.using(() -> { - throw new RuntimeException("forced failure"); - }, r -> Mono.just(1), cleanup::set, false) - .subscribe(ts); + source.subscribe(ts); ts.assertNoValues() .assertNotComplete() @@ -195,16 +236,29 @@ public void resourceThrowsEager() { assertThat(cleanup).hasValue(0); } - @Test - public void factoryThrowsEager() { + @ParameterizedTestWithName + @MethodSource("sourcesThrowNonEager") + public void factoryThrowsNonEager(Mono source) { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); + source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); - Mono.using(() -> 1, r -> { - throw new RuntimeException("forced failure"); - }, cleanup::set, false) - .subscribe(ts); + ts.assertNoValues() + .assertNotComplete() + .assertError(RuntimeException.class) + .assertErrorMessage("forced failure"); + + assertThat(cleanup).hasValue(1); + } + + @ParameterizedTestWithName + @MethodSource("sourcesThrowEager") + public void factoryThrowsEager(Mono source) { + AssertSubscriber ts = AssertSubscriber.create(); + + source.doFinally(event -> assertThat(cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) + .subscribe(ts); ts.assertNoValues() .assertNotComplete() @@ -218,8 +272,6 @@ public void factoryThrowsEager() { public void factoryReturnsNull() { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - Mono.using(() -> 1, r -> null, cleanup::set, @@ -236,8 +288,6 @@ public void factoryReturnsNull() { public void subscriberCancels() { AssertSubscriber ts = AssertSubscriber.create(); - AtomicInteger cleanup = new AtomicInteger(); - Sinks.One tp = Sinks.unsafe().one(); Mono.using(() -> 1, r -> tp.asMono(), cleanup::set, true) From 41dc23343b32463d0613455e13879610689d58b0 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Fri, 2 Feb 2024 00:57:25 +0900 Subject: [PATCH 5/9] Move method for alignment --- .../java/reactor/core/publisher/Flux.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/reactor-core/src/main/java/reactor/core/publisher/Flux.java b/reactor-core/src/main/java/reactor/core/publisher/Flux.java index 5ebfb0c93e..e4e9640e66 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/Flux.java +++ b/reactor-core/src/main/java/reactor/core/publisher/Flux.java @@ -2102,12 +2102,12 @@ public static Flux using(Callable resourceSupplier, Funct } /** - * Uses an {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, - * while streaming the values from a Publisher derived from the same resource and makes sure - * the resource is released if the sequence terminates or the Subscriber cancels. + * Uses a resource, generated by a supplier for each individual Subscriber, while streaming the values from a + * Publisher derived from the same resource and makes sure the resource is released if the sequence terminates or + * the Subscriber cancels. *

- * Eager {@link AutoCloseable} resource cleanup happens just before the source termination and exceptions raised - * by the cleanup Consumer may override the terminal event. + *

  • Eager resource cleanup happens just before the source termination and exceptions raised by the cleanup + * Consumer may override the terminal event.
  • Non-eager cleanup will drop any exception.
*

* *

@@ -2116,6 +2116,8 @@ public static Flux using(Callable resourceSupplier, Funct * * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource + * @param resourceCleanup a resource cleanup callback invoked on completion + * @param eager true to clean before terminating downstream subscribers * @param emitted type * @param resource type * @@ -2123,9 +2125,12 @@ public static Flux using(Callable resourceSupplier, Funct * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) * @see #usingWhen(Publisher, Function, Function) */ - public static Flux using(Callable resourceSupplier, - Function> sourceSupplier) { - return using(resourceSupplier, sourceSupplier, true); + public static Flux using(Callable resourceSupplier, Function> sourceSupplier, Consumer resourceCleanup, boolean eager) { + return onAssembly(new FluxUsing<>(resourceSupplier, + sourceSupplier, + resourceCleanup, + eager)); } /** @@ -2133,11 +2138,8 @@ public static Flux using(Callable r * while streaming the values from a Publisher derived from the same resource and makes sure * the resource is released if the sequence terminates or the Subscriber cancels. *

- *

    - *
  • Eager {@link AutoCloseable} resource cleanup happens just before the source termination and exceptions raised - * by the cleanup Consumer may override the terminal event.
  • - *
  • Non-eager cleanup will drop any exception.
  • - *
+ * Eager {@link AutoCloseable} resource cleanup happens just before the source termination and exceptions raised + * by the cleanup Consumer may override the terminal event. *

* *

@@ -2146,7 +2148,6 @@ public static Flux using(Callable r * * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource - * @param eager true to clean before terminating downstream subscribers * @param emitted type * @param resource type * @@ -2155,17 +2156,20 @@ public static Flux using(Callable r * @see #usingWhen(Publisher, Function, Function) */ public static Flux using(Callable resourceSupplier, - Function> sourceSupplier, boolean eager) { - return using(resourceSupplier, sourceSupplier, Exceptions.AUTO_CLOSE, eager); + Function> sourceSupplier) { + return using(resourceSupplier, sourceSupplier, true); } /** - * Uses a resource, generated by a supplier for each individual Subscriber, while streaming the values from a - * Publisher derived from the same resource and makes sure the resource is released if the sequence terminates or - * the Subscriber cancels. + * Uses an {@link AutoCloseable} resource, generated by a supplier for each individual Subscriber, + * while streaming the values from a Publisher derived from the same resource and makes sure + * the resource is released if the sequence terminates or the Subscriber cancels. *

- *

  • Eager resource cleanup happens just before the source termination and exceptions raised by the cleanup - * Consumer may override the terminal event.
  • Non-eager cleanup will drop any exception.
+ *
    + *
  • Eager {@link AutoCloseable} resource cleanup happens just before the source termination and exceptions raised + * by the cleanup Consumer may override the terminal event.
  • + *
  • Non-eager cleanup will drop any exception.
  • + *
*

* *

@@ -2174,7 +2178,6 @@ public static Flux using(Callable r * * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource - * @param resourceCleanup a resource cleanup callback invoked on completion * @param eager true to clean before terminating downstream subscribers * @param emitted type * @param resource type @@ -2183,12 +2186,9 @@ public static Flux using(Callable r * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) * @see #usingWhen(Publisher, Function, Function) */ - public static Flux using(Callable resourceSupplier, Function> sourceSupplier, Consumer resourceCleanup, boolean eager) { - return onAssembly(new FluxUsing<>(resourceSupplier, - sourceSupplier, - resourceCleanup, - eager)); + public static Flux using(Callable resourceSupplier, + Function> sourceSupplier, boolean eager) { + return using(resourceSupplier, sourceSupplier, Exceptions.AUTO_CLOSE, eager); } /** From 90cd1877ba8ecc32d185aac812ec4daf7ac6f701 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Fri, 2 Feb 2024 02:15:12 +0900 Subject: [PATCH 6/9] Introduce `CleanupCase` on test --- .../reactor/core/publisher/FluxUsingTest.java | 280 +++++++++++++----- .../reactor/core/publisher/MonoUsingTest.java | 279 ++++++++++++----- 2 files changed, 413 insertions(+), 146 deletions(-) diff --git a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java index 0abc979325..c90688da83 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java @@ -20,10 +20,10 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import org.assertj.core.api.Assertions; import org.assertj.core.api.Condition; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; @@ -44,74 +44,185 @@ public class FluxUsingTest extends FluxOperatorTest { - public static List> sourcesNonEager() { + public static List> sourcesNonEager() { return Arrays.asList( - Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set, false), - Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10), false) + new CleanupCase("sourceNonEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set, false); + } + }, + new CleanupCase("autocloseableNonEager") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10), false); + } + } ); } - public static List> sourcesEager() { + public static List> sourcesEager() { return Arrays.asList( - Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set), - Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set, true), - Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10)), - Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10), true) + new CleanupCase("sourceEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set); + } + }, + new CleanupCase("sourceEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.range(r, 10), cleanup::set, true); + } + }, + new CleanupCase("autocloseableEager") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10)); + } + }, + new CleanupCase("autocloseableEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> Flux.range(1, 10), true); + } + } ); } - public static List> sourcesFailNonEager() { + public static List> sourcesFailNonEager() { return Arrays.asList( - Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set, false), - Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure")), false) + new CleanupCase("sourceFailNonEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set, false); + } + }, + new CleanupCase("autocloseableFailNonEager") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure")), false); + } + } ); } - public static List> sourcesFailEager() { + public static List> sourcesFailEager() { return Arrays.asList( - Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set), - Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set, true), - Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure"))), - Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure")), true) + new CleanupCase("sourceFailEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set); + } + }, + new CleanupCase("sourceFailEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.error(new RuntimeException("forced failure")), cleanup::set, true); + } + }, + new CleanupCase("autocloseableFailEager") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure"))); + } + }, + new CleanupCase("autocloseableFailEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> Flux.error(new RuntimeException("forced failure")), true); + } + } ); } - public static List> resourcesThrow() { + public static List> resourcesThrow() { return Arrays.asList( - // non eager - Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set, false), - Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10), false), - // eager - Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set), - Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set, true), - Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10)), - Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10), true) + new CleanupCase("resourceThrowNonEager") { + @Override + public Flux get() { + return Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set, false); + } + }, + new CleanupCase("autocloseableResourceThrowNonEager") { + @Override + public Flux get() { + return Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10), false); + } + }, + new CleanupCase("resourceThrowEager") { + @Override + public Flux get() { + return Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set); + } + }, + new CleanupCase("resourceThrowEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(r, 10), cleanup::set, true); + } + }, + new CleanupCase("autocloseableResourceThrowEager") { + @Override + public Flux get() { + return Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10)); + } + }, + new CleanupCase("autocloseableResourceThrowEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> { throw new RuntimeException("forced failure"); }, r -> Flux.range(1, 10), true); + } + } ); } - public static List> sourcesThrowNonEager() { + public static List> sourcesThrowNonEager() { return Arrays.asList( - Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, false), - Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, false) + new CleanupCase("sourceThrowNonEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, false); + } + }, + new CleanupCase("autocloseableThrowNonEager") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, false); + } + } ); } - public static List> sourcesThrowEager() { + public static List> sourcesThrowEager() { return Arrays.asList( - Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set), - Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, true), - Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }), - Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, true) + new CleanupCase("sourceThrowEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set); + } + }, + new CleanupCase("sourceThrowEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, true); + } + }, + new CleanupCase("autocloseableThrowEager") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }); + } + }, + new CleanupCase("autocloseableThrowEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, true); + } + } ); } - private static final AtomicInteger cleanup = new AtomicInteger(); - - @BeforeEach - public void before() { - cleanup.set(0); - } - @Override protected Scenario defaultScenarioOptions(Scenario defaultOptions) { return defaultOptions.fusionMode(Fuseable.ANY) @@ -187,52 +298,53 @@ public void resourceCleanupNull() { @ParameterizedTestWithName @MethodSource("sourcesNonEager") - public void normal(Flux source) { + public void normal(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); + cleanupCase.get().doAfterTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(0)).subscribe(ts); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertComplete() .assertNoError(); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } @ParameterizedTestWithName @MethodSource("sourcesEager") - public void normalEager(Flux source) { + public void normalEager(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doFinally(event -> assertThat(cleanup).hasValue(0)) - .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) - .subscribe(ts); + cleanupCase.get() + .doFinally(event -> assertThat(cleanupCase.cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(1)) + .subscribe(ts); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .assertComplete() .assertNoError(); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } - void checkCleanupExecutionTime(Flux source, boolean eager, boolean fail) { + void checkCleanupExecutionTime(CleanupCase cleanupCase, boolean eager, boolean fail) { AtomicBoolean before = new AtomicBoolean(); AssertSubscriber ts = new AssertSubscriber() { @Override public void onError(Throwable t) { super.onError(t); - before.set(cleanup.get() != 0); + before.set(cleanupCase.cleanup.get() != 0); } @Override public void onComplete() { super.onComplete(); - before.set(cleanup.get() != 0); + before.set(cleanupCase.cleanup.get() != 0); } }; - source.subscribe(ts); + cleanupCase.get().subscribe(ts); if (fail) { ts.assertNoValues() @@ -246,85 +358,88 @@ public void onComplete() { .assertNoError(); } - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); assertThat(before.get()).isEqualTo(eager); } @ParameterizedTestWithName @MethodSource("sourcesNonEager") - public void checkNonEager(Flux source) { - checkCleanupExecutionTime(source, false, false); + public void checkNonEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, false, false); } @ParameterizedTestWithName @MethodSource("sourcesEager") - public void checkEager(Flux source) { - checkCleanupExecutionTime(source, true, false); + public void checkEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, true, false); } @ParameterizedTestWithName @MethodSource("sourcesFailNonEager") - public void checkErrorNonEager(Flux source) { - checkCleanupExecutionTime(source, false, true); + public void checkErrorNonEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, false, true); } @ParameterizedTestWithName @MethodSource("sourcesFailEager") - public void checkErrorEager(Flux source) { - checkCleanupExecutionTime(source, true, true); + public void checkErrorEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, true, true); } @ParameterizedTestWithName @MethodSource("resourcesThrow") - public void resourceThrows(Flux source) { + public void resourceThrows(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.subscribe(ts); + cleanupCase.get().subscribe(ts); ts.assertNoValues() .assertNotComplete() .assertError(RuntimeException.class) .assertErrorMessage("forced failure"); - assertThat(cleanup).hasValue(0); + assertThat(cleanupCase.cleanup).hasValue(0); } @ParameterizedTestWithName @MethodSource("sourcesThrowNonEager") - public void factoryThrowsNonEager(Flux source) { + public void factoryThrowsNonEager(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); + cleanupCase.get().doAfterTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(0)).subscribe(ts); ts.assertNoValues() .assertNotComplete() .assertError(RuntimeException.class) .assertErrorMessage("forced failure"); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } @ParameterizedTestWithName @MethodSource("sourcesThrowEager") - public void factoryThrowsEager(Flux source) { + public void factoryThrowsEager(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doFinally(event -> assertThat(cleanup).hasValue(0)) - .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) - .subscribe(ts); + cleanupCase.get() + .doFinally(event -> assertThat(cleanupCase.cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(1)) + .subscribe(ts); ts.assertNoValues() .assertNotComplete() .assertError(RuntimeException.class) .assertErrorMessage("forced failure"); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } @Test public void factoryReturnsNull() { AssertSubscriber ts = AssertSubscriber.create(); + AtomicInteger cleanup = new AtomicInteger(); + Flux.using(() -> 1, r -> null, cleanup::set, @@ -341,6 +456,8 @@ public void factoryReturnsNull() { public void subscriberCancels() { AssertSubscriber ts = AssertSubscriber.create(); + AtomicInteger cleanup = new AtomicInteger(); + Sinks.Many tp = Sinks.unsafe().many().multicast().directBestEffort(); Flux.using(() -> 1, r -> tp.asFlux(), cleanup::set, true) @@ -392,6 +509,8 @@ public void sourceFactoryAndResourceCleanupThrow() { @Test public void scanOperator(){ + AtomicInteger cleanup = new AtomicInteger(); + FluxUsing test = new FluxUsing<>(() -> 1, r -> Flux.range(r, 10), cleanup::set, false); assertThat(test.scan(Scannable.Attr.RUN_STYLE)).isSameAs(Scannable.Attr.RunStyle.SYNC); @@ -455,4 +574,19 @@ public void scanFuseableSubscriber() { Assertions.assertThat(test.scan(Scannable.Attr.CANCELLED)).isTrue(); } + static abstract class CleanupCase implements Supplier> { + + final AtomicInteger cleanup = new AtomicInteger(); + final String name; + + CleanupCase( String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + } diff --git a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java index 26ea51faa5..66a97af607 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java @@ -22,9 +22,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import org.assertj.core.api.Condition; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.provider.MethodSource; @@ -41,74 +41,185 @@ public class MonoUsingTest { - public static List> sourcesNonEager() { + public static List> sourcesNonEager() { return Arrays.asList( - Mono.using(() -> 1, Mono::just, cleanup::set, false), - Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1), false) + new CleanupCase("sourceNonEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, Mono::just, cleanup::set, false); + } + }, + new CleanupCase("autocloseableNonEager") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1), false); + } + } ); } - public static List> sourcesEager() { + public static List> sourcesEager() { return Arrays.asList( - Mono.using(() -> 1, Mono::just, cleanup::set), - Mono.using(() -> 1, Mono::just, cleanup::set, true), - Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1)), - Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1), true) + new CleanupCase("sourceEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, Mono::just, cleanup::set); + } + }, + new CleanupCase("sourceEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> 1, Mono::just, cleanup::set, true); + } + }, + new CleanupCase("autocloseableEager") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1)); + } + }, + new CleanupCase("autocloseableEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> Mono.just(1), true); + } + } ); } - public static List> sourcesFailNonEager() { + public static List> sourcesFailNonEager() { return Arrays.asList( - Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set, false), - Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure")), false) + new CleanupCase("sourceFailNonEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set, false); + } + }, + new CleanupCase("autocloseableFailNonEager") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure")), false); + } + } ); } - public static List> sourcesFailEager() { + public static List> sourcesFailEager() { return Arrays.asList( - Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set), - Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set, true), - Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure"))), - Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure")), true) + new CleanupCase("sourceFailEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set); + } + }, + new CleanupCase("sourceFailEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> 1, r -> Mono.error(new RuntimeException("forced failure")), cleanup::set, true); + } + }, + new CleanupCase("autocloseableFailEager") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure"))); + } + }, + new CleanupCase("autocloseableFailEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> Mono.error(new RuntimeException("forced failure")), true); + } + } ); } - public static List> resourcesThrow() { + public static List> resourcesThrow() { return Arrays.asList( - // non eager - Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set, false), - Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1), false), - // eager - Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set), - Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set, true), - Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1)), - Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1), true) + new CleanupCase("resourceThrowNonEager") { + @Override + public Mono get() { + return Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set, false); + } + }, + new CleanupCase("autocloseableResourceThrowNonEager") { + @Override + public Mono get() { + return Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1), false); + } + }, + new CleanupCase("resourceThrowEager") { + @Override + public Mono get() { + return Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set); + } + }, + new CleanupCase("resourceThrowEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> { throw new RuntimeException("forced failure"); }, Mono::just, cleanup::set, true); + } + }, + new CleanupCase("autocloseableResourceThrowEager") { + @Override + public Mono get() { + return Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1)); + } + }, + new CleanupCase("autocloseableResourceThrowEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> { throw new RuntimeException("forced failure"); }, r -> Mono.just(1), true); + } + } ); } - public static List> sourcesThrowNonEager() { + public static List> sourcesThrowNonEager() { return Arrays.asList( - Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, false), - Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, false) + new CleanupCase("sourceThrowNonEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, false); + } + }, + new CleanupCase("autocloseableThrowNonEager") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, false); + } + } ); } - public static List> sourcesThrowEager() { + public static List> sourcesThrowEager() { return Arrays.asList( - Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set), - Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, true), - Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }), - Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, true) + new CleanupCase("sourceThrowEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set); + } + }, + new CleanupCase("sourceThrowEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> 1, r -> { throw new RuntimeException("forced failure"); }, cleanup::set, true); + } + }, + new CleanupCase("autocloseableThrowEager") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }); + } + }, + new CleanupCase("autocloseableThrowEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> cleanup::incrementAndGet, r -> { throw new RuntimeException("forced failure"); }, true); + } + } ); } - private static final AtomicInteger cleanup = new AtomicInteger(); - - @BeforeEach - public void before() { - cleanup.set(0); - } - @Test public void resourceSupplierNull() { assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> { @@ -134,52 +245,53 @@ public void resourceCleanupNull() { @ParameterizedTestWithName @MethodSource("sourcesNonEager") - public void normal(Mono source) { + public void normal(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); + cleanupCase.get().doAfterTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(0)).subscribe(ts); ts.assertValues(1) .assertComplete() .assertNoError(); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } @ParameterizedTestWithName @MethodSource("sourcesEager") - public void normalEager(Mono source) { + public void normalEager(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doFinally(event -> assertThat(cleanup).hasValue(0)) - .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) - .subscribe(ts); + cleanupCase.get() + .doFinally(event -> assertThat(cleanupCase.cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(1)) + .subscribe(ts); ts.assertValues(1) .assertComplete() .assertNoError(); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } - void checkCleanupExecutionTime(Mono source, boolean eager, boolean fail) { + void checkCleanupExecutionTime(CleanupCase cleanupCase, boolean eager, boolean fail) { AtomicBoolean before = new AtomicBoolean(); AssertSubscriber ts = new AssertSubscriber() { @Override public void onError(Throwable t) { super.onError(t); - before.set(cleanup.get() != 0); + before.set(cleanupCase.cleanup.get() != 0); } @Override public void onComplete() { super.onComplete(); - before.set(cleanup.get() != 0); + before.set(cleanupCase.cleanup.get() != 0); } }; - source.subscribe(ts); + cleanupCase.get().subscribe(ts); if (fail) { ts.assertNoValues() @@ -193,85 +305,88 @@ public void onComplete() { .assertNoError(); } - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); assertThat(before.get()).isEqualTo(eager); } @ParameterizedTestWithName @MethodSource("sourcesNonEager") - public void checkNonEager(Mono source) { - checkCleanupExecutionTime(source, false, false); + public void checkNonEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, false, false); } @ParameterizedTestWithName @MethodSource("sourcesEager") - public void checkEager(Mono source) { - checkCleanupExecutionTime(source, true, false); + public void checkEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, true, false); } @ParameterizedTestWithName @MethodSource("sourcesFailNonEager") - public void checkErrorNonEager(Mono source) { - checkCleanupExecutionTime(source, false, true); + public void checkErrorNonEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, false, true); } @ParameterizedTestWithName @MethodSource("sourcesFailEager") - public void checkErrorEager(Mono source) { - checkCleanupExecutionTime(source, true, true); + public void checkErrorEager(CleanupCase cleanupCase) { + checkCleanupExecutionTime(cleanupCase, true, true); } @ParameterizedTestWithName @MethodSource("resourcesThrow") - public void resourceThrowsEager(Mono source) { + public void resourceThrowsEager(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.subscribe(ts); + cleanupCase.get().subscribe(ts); ts.assertNoValues() .assertNotComplete() .assertError(RuntimeException.class) .assertErrorMessage("forced failure"); - assertThat(cleanup).hasValue(0); + assertThat(cleanupCase.cleanup).hasValue(0); } @ParameterizedTestWithName @MethodSource("sourcesThrowNonEager") - public void factoryThrowsNonEager(Mono source) { + public void factoryThrowsNonEager(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doAfterTerminate(() -> assertThat(cleanup).hasValue(0)).subscribe(ts); + cleanupCase.get().doAfterTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(0)).subscribe(ts); ts.assertNoValues() .assertNotComplete() .assertError(RuntimeException.class) .assertErrorMessage("forced failure"); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } @ParameterizedTestWithName @MethodSource("sourcesThrowEager") - public void factoryThrowsEager(Mono source) { + public void factoryThrowsEager(CleanupCase cleanupCase) { AssertSubscriber ts = AssertSubscriber.create(); - source.doFinally(event -> assertThat(cleanup).hasValue(0)) - .doOnTerminate(() -> assertThat(cleanup).hasValue(1)) - .subscribe(ts); + cleanupCase.get() + .doFinally(event -> assertThat(cleanupCase.cleanup).hasValue(0)) + .doOnTerminate(() -> assertThat(cleanupCase.cleanup).hasValue(1)) + .subscribe(ts); ts.assertNoValues() .assertNotComplete() .assertError(RuntimeException.class) .assertErrorMessage("forced failure"); - assertThat(cleanup).hasValue(1); + assertThat(cleanupCase.cleanup).hasValue(1); } @Test public void factoryReturnsNull() { AssertSubscriber ts = AssertSubscriber.create(); + AtomicInteger cleanup = new AtomicInteger(); + Mono.using(() -> 1, r -> null, cleanup::set, @@ -288,6 +403,8 @@ public void factoryReturnsNull() { public void subscriberCancels() { AssertSubscriber ts = AssertSubscriber.create(); + AtomicInteger cleanup = new AtomicInteger(); + Sinks.One tp = Sinks.unsafe().one(); Mono.using(() -> 1, r -> tp.asMono(), cleanup::set, true) @@ -452,4 +569,20 @@ public void scanSubscriber() { test.cancel(); assertThat(test.scan(Scannable.Attr.CANCELLED)).isTrue(); } + + static abstract class CleanupCase implements Supplier> { + + final AtomicInteger cleanup = new AtomicInteger(); + final String name; + + CleanupCase( String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + } From 21d148264a7509a28d03779ecfca8477505761df Mon Sep 17 00:00:00 2001 From: injae-kim Date: Fri, 2 Feb 2024 03:13:55 +0900 Subject: [PATCH 7/9] Add resourcesCleanupThrow tests --- .../reactor/core/publisher/FluxUsingTest.java | 91 +++++++++++++++++++ .../reactor/core/publisher/MonoUsingTest.java | 91 +++++++++++++++++++ 2 files changed, 182 insertions(+) diff --git a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java index c90688da83..584057766d 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java @@ -223,6 +223,67 @@ public Flux get() { ); } + public static List> resourcesCleanupThrowNonEager() { + return Arrays.asList( + new CleanupCase("resourceCleanupThrowNonEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.range(r, 10), r -> { throw new IllegalStateException("resourceCleanup"); }, false); + } + }, + new CleanupCase("autocloseableResourceCleanupThrowNonEager") { + @Override + public Flux get() { + return Flux.using(() -> new AutoCloseable() { + @Override + public void close() { + throw new IllegalStateException("resourceCleanup"); + } + }, r -> Flux.range(1, 10), false); + } + } + ); + } + + public static List> resourcesCleanupThrowEager() { + return Arrays.asList( + new CleanupCase("resourceCleanupThrowEager") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.range(r, 10), r -> { throw new IllegalStateException("resourceCleanup"); }); + } + }, + new CleanupCase("resourceCleanupThrowEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> 1, r -> Flux.range(r, 10), r -> { throw new IllegalStateException("resourceCleanup"); }, true); + } + }, + new CleanupCase("autocloseableResourceCleanupThrowEager") { + @Override + public Flux get() { + return Flux.using(() -> new AutoCloseable() { + @Override + public void close() { + throw new IllegalStateException("resourceCleanup"); + } + }, r -> Flux.range(1, 10)); + } + }, + new CleanupCase("autocloseableResourceCleanupThrowEagerFlag") { + @Override + public Flux get() { + return Flux.using(() -> new AutoCloseable() { + @Override + public void close() { + throw new IllegalStateException("resourceCleanup"); + } + }, r -> Flux.range(1, 10), true); + } + } + ); + } + @Override protected Scenario defaultScenarioOptions(Scenario defaultOptions) { return defaultOptions.fusionMode(Fuseable.ANY) @@ -434,6 +495,36 @@ public void factoryThrowsEager(CleanupCase cleanupCase) { assertThat(cleanupCase.cleanup).hasValue(1); } + @ParameterizedTestWithName + @MethodSource("resourcesCleanupThrowNonEager") + public void resourcesCleanupThrowNonEager(CleanupCase cleanupCase) { + AssertSubscriber ts = AssertSubscriber.create(); + + cleanupCase.get().subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + .assertComplete() + .assertNoError(); + + assertThat(cleanupCase.cleanup).hasValue(0); + } + + @ParameterizedTestWithName + @MethodSource("resourcesCleanupThrowEager") + public void resourcesCleanupThrowEager(CleanupCase cleanupCase) { + AssertSubscriber ts = AssertSubscriber.create(); + + cleanupCase.get().subscribe(ts); + + ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + .assertErrorWith(e -> { + assertThat(e).hasMessage("resourceCleanup"); + assertThat(e).isExactlyInstanceOf(IllegalStateException.class); + }); + + assertThat(cleanupCase.cleanup).hasValue(0); + } + @Test public void factoryReturnsNull() { AssertSubscriber ts = AssertSubscriber.create(); diff --git a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java index 66a97af607..5ab3b6f172 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java @@ -220,6 +220,67 @@ public Mono get() { ); } + public static List> resourcesCleanupThrowNonEager() { + return Arrays.asList( + new CleanupCase("resourceCleanupThrowNonEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, Mono::just, r -> { throw new IllegalStateException("resourceCleanup"); }, false); + } + }, + new CleanupCase("autocloseableResourceCleanupThrowNonEager") { + @Override + public Mono get() { + return Mono.using(() -> new AutoCloseable() { + @Override + public void close() { + throw new IllegalStateException("resourceCleanup"); + } + }, r -> Mono.just(1), false); + } + } + ); + } + + public static List> resourcesCleanupThrowEager() { + return Arrays.asList( + new CleanupCase("resourceCleanupThrowEager") { + @Override + public Mono get() { + return Mono.using(() -> 1, Mono::just, r -> { throw new IllegalStateException("resourceCleanup"); }); + } + }, + new CleanupCase("resourceCleanupThrowEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> 1, Mono::just, r -> { throw new IllegalStateException("resourceCleanup"); }, true); + } + }, + new CleanupCase("autocloseableResourceCleanupThrowEager") { + @Override + public Mono get() { + return Mono.using(() -> new AutoCloseable() { + @Override + public void close() { + throw new IllegalStateException("resourceCleanup"); + } + }, r -> Mono.just(1)); + } + }, + new CleanupCase("autocloseableResourceCleanupThrowEagerFlag") { + @Override + public Mono get() { + return Mono.using(() -> new AutoCloseable() { + @Override + public void close() { + throw new IllegalStateException("resourceCleanup"); + } + }, r -> Mono.just(1), true); + } + } + ); + } + @Test public void resourceSupplierNull() { assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> { @@ -381,6 +442,36 @@ public void factoryThrowsEager(CleanupCase cleanupCase) { assertThat(cleanupCase.cleanup).hasValue(1); } + @ParameterizedTestWithName + @MethodSource("resourcesCleanupThrowNonEager") + public void resourcesCleanupThrowNonEager(CleanupCase cleanupCase) { + AssertSubscriber ts = AssertSubscriber.create(); + + cleanupCase.get().subscribe(ts); + + ts.assertValues(1) + .assertComplete() + .assertNoError(); + + assertThat(cleanupCase.cleanup).hasValue(0); + } + + @ParameterizedTestWithName + @MethodSource("resourcesCleanupThrowEager") + public void resourcesCleanupThrowEager(CleanupCase cleanupCase) { + AssertSubscriber ts = AssertSubscriber.create(); + + cleanupCase.get().subscribe(ts); + + ts.assertNoValues() + .assertErrorWith(e -> { + assertThat(e).hasMessage("resourceCleanup"); + assertThat(e).isExactlyInstanceOf(IllegalStateException.class); + }); + + assertThat(cleanupCase.cleanup).hasValue(0); + } + @Test public void factoryReturnsNull() { AssertSubscriber ts = AssertSubscriber.create(); From c248e386fad9b54d5ec8f8d9f6e6585cc875ab13 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Fri, 2 Feb 2024 03:14:41 +0900 Subject: [PATCH 8/9] Add methodExcludes to fix japicmp --- reactor-core/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/reactor-core/build.gradle b/reactor-core/build.gradle index 863ba333eb..0394410e4c 100644 --- a/reactor-core/build.gradle +++ b/reactor-core/build.gradle @@ -256,6 +256,10 @@ task japicmp(type: JapicmpTask) { classExcludes = [ ] methodExcludes = [ + "reactor.core.publisher.Flux#using(java.util.concurrent.Callable, java.util.function.Function)", + "reactor.core.publisher.Flux#using(java.util.concurrent.Callable, java.util.function.Function, boolean)", + "reactor.core.publisher.Mono#using(java.util.concurrent.Callable, java.util.function.Function)", + "reactor.core.publisher.Mono#using(java.util.concurrent.Callable, java.util.function.Function, boolean)" ] } From 546cdd61965c362ee77e17d3d4d4cf968573b487 Mon Sep 17 00:00:00 2001 From: injae-kim Date: Fri, 2 Feb 2024 03:17:05 +0900 Subject: [PATCH 9/9] Fix typo --- .../src/test/java/reactor/core/publisher/FluxUsingTest.java | 2 +- .../src/test/java/reactor/core/publisher/MonoUsingTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java index 584057766d..71ab689ff3 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/FluxUsingTest.java @@ -670,7 +670,7 @@ static abstract class CleanupCase implements Supplier> { final AtomicInteger cleanup = new AtomicInteger(); final String name; - CleanupCase( String name) { + CleanupCase(String name) { this.name = name; } diff --git a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java index 5ab3b6f172..620e85da69 100644 --- a/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java +++ b/reactor-core/src/test/java/reactor/core/publisher/MonoUsingTest.java @@ -666,7 +666,7 @@ static abstract class CleanupCase implements Supplier> { final AtomicInteger cleanup = new AtomicInteger(); final String name; - CleanupCase( String name) { + CleanupCase(String name) { this.name = name; }