From f6d7d1b31c3c1b4b252b5ca0c785484bff1318da Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Mon, 15 Mar 2021 18:35:54 -0700 Subject: [PATCH 1/8] Publisher, Single, Completable onError* operators Motivation: ServiceTalk supports a recoverWith operator which allows for error recovery [1]. However the signature of this method is more complex than is often required for simple error recovery, and the name implies "recovery" which is confusing if the return value is intended to propagate an error. [1] http://reactivex.io/documentation/operators/catch.html Modifications: - Add onError* methods to Publisher, Single, and Completable. The signatures are specialized for their respective operation (mapping Throwable, swallowing exceptions, recovering with a value) and also supports filtering by class type and custom Predicate. This naming convention is more commonly used by other JVM based rx implementations and is assumed to be more familiar to users. - Deprecate recoverWith in favor of onErrorResume Result: Publisher, Single, and Completable have onError* operators which provide more APIs tailored toward common error recovery scenarios. --- .../concurrent/api/AsyncCloseables.java | 6 +- .../concurrent/api/Completable.java | 212 ++++++++++- .../api/DefaultCompositeCloseable.java | 16 +- .../servicetalk/concurrent/api/Publisher.java | 336 +++++++++++++++++- .../concurrent/api/ResumeCompletable.java | 8 +- .../concurrent/api/ResumePublisher.java | 8 +- .../concurrent/api/ResumeSingle.java | 8 +- .../io/servicetalk/concurrent/api/Single.java | 263 +++++++++++++- .../api/OnErrorCompletableTest.java | 159 +++++++++ .../concurrent/api/OnErrorPublisherTest.java | 212 +++++++++++ ...t.java => OnErrorResumePublisherTest.java} | 20 +- ...Test.java => OnErrorResumeSingleTest.java} | 6 +- .../concurrent/api/OnErrorSingleTest.java | 169 +++++++++ .../api/PublisherConcatMapIterableTest.java | 2 +- .../api/PublisherFlatMapMergeTest.java | 4 +- .../api/PublisherFlatMapSingleTest.java | 2 +- ...ompletableOnErrorCompleteClassTckTest.java | 29 ++ ...etableOnErrorCompletePredicateTckTest.java | 29 ++ .../CompletableOnErrorCompleteTckTest.java | 28 ++ .../CompletableOnErrorMapClassTckTest.java | 29 ++ ...CompletableOnErrorMapPredicateTckTest.java | 29 ++ .../tck/CompletableOnErrorMapTckTest.java | 28 ++ .../CompletableOnErrorResumeClassTckTest.java | 34 ++ ...pletableOnErrorResumePredicateTckTest.java | 34 ++ .../tck/CompletableOnErrorResumeTckTest.java | 9 +- .../PublisherOnErrorCompleteClassTckTest.java | 29 ++ ...lisherOnErrorCompletePredicateTckTest.java | 29 ++ .../tck/PublisherOnErrorCompleteTckTest.java | 28 ++ .../tck/PublisherOnErrorMapClassTckTest.java | 31 ++ .../PublisherOnErrorMapPredicateTckTest.java | 31 ++ .../tck/PublisherOnErrorMapTckTest.java | 30 ++ .../PublisherOnErrorResumeClassTckTest.java | 37 ++ ...ublisherOnErrorResumePredicateTckTest.java | 37 ++ .../tck/PublisherOnErrorResumeTckTest.java | 2 +- .../PublisherOnErrorReturnClassTckTest.java | 29 ++ ...ublisherOnErrorReturnPredicateTckTest.java | 29 ++ .../tck/PublisherOnErrorReturnTckTest.java | 28 ++ .../tck/SingleOnErrorMapClassTckTest.java | 29 ++ .../tck/SingleOnErrorMapPredicateTckTest.java | 29 ++ .../tck/SingleOnErrorMapTckTest.java | 28 ++ .../tck/SingleOnErrorResumeClassTckTest.java | 34 ++ .../SingleOnErrorResumePredicateTckTest.java | 34 ++ .../tck/SingleOnErrorResumeTckTest.java | 9 +- .../tck/SingleOnErrorReturnClassTckTest.java | 29 ++ .../SingleOnErrorReturnPredicateTckTest.java | 29 ++ .../tck/SingleOnErrorReturnTckTest.java | 28 ++ .../dns/discovery/netty/DefaultDnsClient.java | 4 +- .../netty/NettyChannelContentCodecTest.java | 4 +- .../BadResponseHandlingServiceFilter.java | 2 +- .../service/composition/GatewayService.java | 2 +- .../composition/StreamingGatewayService.java | 4 +- .../grpc/api/GrpcClientBuilder.java | 2 +- .../io/servicetalk/grpc/api/GrpcRouter.java | 17 +- .../grpc/api/GrpcServerBuilder.java | 2 +- .../api/DefaultHttpExecutionStrategy.java | 3 +- .../DefaultServiceDiscoveryRetryStrategy.java | 2 +- .../http/netty/NettyHttpServer.java | 8 +- .../ProxyConnectConnectionFactoryFilter.java | 2 +- .../HttpAuthConnectionFactoryClientTest.java | 2 +- .../auth/BasicAuthHttpServiceFilter.java | 2 +- .../publisher/reporter/HttpReporter.java | 4 +- .../internal/DefaultNettyConnection.java | 13 +- 62 files changed, 2244 insertions(+), 98 deletions(-) create mode 100644 servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java create mode 100644 servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java rename servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/{ResumePublisherTest.java => OnErrorResumePublisherTest.java} (88%) rename servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/{ResumeSingleTest.java => OnErrorResumeSingleTest.java} (95%) create mode 100644 servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompletePredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapPredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumePredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompletePredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapPredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumePredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnPredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapPredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumePredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnClassTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnPredicateTckTest.java create mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnTckTest.java diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AsyncCloseables.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AsyncCloseables.java index 811a61c10e..4000862fce 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AsyncCloseables.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AsyncCloseables.java @@ -23,7 +23,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import static io.servicetalk.concurrent.api.Completable.failed; import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater; import static java.util.function.Function.identity; @@ -46,9 +45,8 @@ private AsyncCloseables() { * @return A {@link Completable} that is notified once the close is complete. */ public static Completable closeAsyncGracefully(AsyncCloseable closable, long timeout, TimeUnit timeoutUnit) { - return closable.closeAsyncGracefully().timeout(timeout, timeoutUnit).onErrorResume( - t -> t instanceof TimeoutException ? closable.closeAsync() : failed(t) - ); + return closable.closeAsyncGracefully().timeout(timeout, timeoutUnit).onErrorResume(TimeoutException.class, + t -> closable.closeAsync()); } /** diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java index 2537ab81e6..fe8131771e 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java @@ -35,6 +35,7 @@ import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.IntPredicate; +import java.util.function.Predicate; import java.util.function.Supplier; import static io.servicetalk.concurrent.api.CompletableDoOnUtils.doOnCompleteSupplier; @@ -103,6 +104,213 @@ protected Completable() { // Operators Begin // + /** + * Transform errors emitted on this {@link Completable} into a {@link Subscriber#onComplete()} signal + * (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         // ignored
+     *     }
+     * }
+ * @return A {@link Completable} which transform errors emitted on this {@link Completable} into a + * {@link Subscriber#onComplete()} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Completable onErrorComplete() { + return onErrorComplete(t -> true); + } + + /** + * Transform errors emitted on this {@link Completable} which match {@code type} into a + * {@link Subscriber#onComplete()} signal (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         if (!type.isInstance(cause)) {
+     *           throw cause;
+     *         }
+     *     }
+     * }
+ * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param The {@link Throwable} type. + * @return A {@link Completable} which transform errors emitted on this {@link Completable} which match {@code type} + * into a {@link Subscriber#onComplete()} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Completable onErrorComplete(Class type) { + return onErrorComplete(type::isInstance); + } + + /** + * Transform errors emitted on this {@link Completable} which match {@code predicate} into a + * {@link Subscriber#onComplete()} signal (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         if (!predicate.test(cause)) {
+     *           throw cause;
+     *         }
+     *     }
+     * }
+ * @param predicate returns {@code true} if the {@link Throwable} should be transformed to and + * {@link Subscriber#onComplete()} signal. Returns {@code false} to propagate the error. + * @return A {@link Completable} which transform errors emitted on this {@link Completable} which match + * {@code predicate} into a {@link Subscriber#onComplete()} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Completable onErrorComplete(Predicate predicate) { + return onErrorResume(predicate, t -> Completable.completed()); + } + + /** + * Transform errors emitted on this {@link Completable} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         throw mapper.apply(cause);
+     *     }
+     * }
+ * @param mapper returns the error used to terminate the returned {@link Completable}. + * @return A {@link Completable} which transform errors emitted on this {@link Completable} into a different error. + * @see ReactiveX catch operator. + */ + public final Completable onErrorMap(Function mapper) { + return onErrorMap(t -> true, mapper); + } + + /** + * Transform errors emitted on this {@link Completable} which match {@code type} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         if (type.isInstance(cause)) {
+     *           throw mapper.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     * }
+ * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param mapper returns the error used to terminate the returned {@link Completable}. + * @param The type of {@link Throwable} to transform. + * @return A {@link Completable} which transform errors emitted on this {@link Completable} into a different error. + * @see ReactiveX catch operator. + */ + public final Completable onErrorMap( + Class type, Function mapper) { + @SuppressWarnings("unchecked") + final Function rawMapper = (Function) mapper; + return onErrorMap(type::isInstance, rawMapper); + } + + /** + * Transform errors emitted on this {@link Completable} which match {@code predicate} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         if (predicate.test(cause)) {
+     *           throw mapper.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     * }
+ * @param predicate returns {@code true} if the {@link Throwable} should be transformed via {@code mapper}. Returns + * {@code false} to propagate the original error. + * @param mapper returns the error used to terminate the returned {@link Completable}. + * @return A {@link Completable} which transform errors emitted on this {@link Completable} into a different error. + * @see ReactiveX catch operator. + */ + public final Completable onErrorMap(Predicate predicate, + Function mapper) { + requireNonNull(mapper); + return onErrorResume(predicate, t -> Completable.failed(mapper.apply(t))); + } + + /** + * Recover from errors emitted by this {@link Completable} which match {@code type} by using another + * {@link Completable} provided by the passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         if (type.isInstance(cause)) {
+     *           // Note nextFactory returning a error Completable is like re-throwing (nextFactory shouldn't throw).
+     *           results = nextFactory.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     * }
+ * + * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param nextFactory Returns the next {@link Completable}, when this {@link Completable} emits an error. + * @param The type of {@link Throwable} to transform. + * @return A {@link Completable} that recovers from an error from this {@code Publisher} by using another + * {@link Completable} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Completable onErrorResume( + Class type, Function nextFactory) { + @SuppressWarnings("unchecked") + Function rawNextFactory = + (Function) nextFactory; + return onErrorResume(type::isInstance, rawNextFactory); + } + + /** + * Recover from errors emitted by this {@link Completable} which match {@code predicate} by using another + * {@link Completable} provided by the passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         if (predicate.test(cause)) {
+     *           // Note that nextFactory returning a error Publisher is like re-throwing (nextFactory shouldn't throw).
+     *           results = nextFactory.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     * }
+ * + * @param predicate returns {@code true} if the {@link Throwable} should be transformed via {@code nextFactory}. + * Returns {@code false} to propagate the original error. + * @param nextFactory Returns the next {@link Completable}, when this {@link Completable} emits an error. + * @return A {@link Completable} that recovers from an error from this {@link Completable} by using another + * {@link Completable} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Completable onErrorResume(Predicate predicate, + Function nextFactory) { + requireNonNull(predicate); + requireNonNull(nextFactory); + return onErrorResume(t -> predicate.test(t) ? nextFactory.apply(t) : Completable.failed(t)); + } + /** * Recover from any error emitted by this {@link Completable} by using another {@link Completable} provided by the * passed {@code nextFactory}. @@ -118,10 +326,10 @@ protected Completable() { * } * * @param nextFactory Returns the next {@link Completable}, if this {@link Completable} emits an error. - * @return A {@link Completable} that recovers from an error from this {@code Completable} by using another + * @return A {@link Completable} that recovers from an error from this {@link Completable} by using another * {@link Completable} provided by the passed {@code nextFactory}. */ - public final Completable onErrorResume(Function nextFactory) { + public final Completable onErrorResume(Function nextFactory) { return new ResumeCompletable(this, nextFactory, executor); } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/DefaultCompositeCloseable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/DefaultCompositeCloseable.java index 1ed6edd73c..9601540ff9 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/DefaultCompositeCloseable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/DefaultCompositeCloseable.java @@ -123,28 +123,28 @@ private void mergeCloseableDelayError(final List closeables) { } private void concatCloseableDelayError(final AsyncCloseable closeable) { - closeAsync = closeAsync.concat(closeable.closeAsync().onErrorResume(th -> { + closeAsync = closeAsync.concat(closeable.closeAsync().onErrorComplete(th -> { //TODO: This should use concatDelayError when available. LOGGER.debug("Ignored failure to close {}.", closeable, th); - return completed(); + return true; })); - closeAsyncGracefully = closeAsyncGracefully.concat(closeable.closeAsyncGracefully().onErrorResume(th -> { + closeAsyncGracefully = closeAsyncGracefully.concat(closeable.closeAsyncGracefully().onErrorComplete(th -> { //TODO: This should use concatDelayError when available. LOGGER.debug("Ignored failure to close {}.", closeable, th); - return completed(); + return true; })); } private void prependCloseableDelayError(final AsyncCloseable closeable) { - closeAsync = closeable.closeAsync().onErrorResume(th -> { + closeAsync = closeable.closeAsync().onErrorComplete(th -> { //TODO: This should use prependDelayError when available. LOGGER.debug("Ignored failure to close {}.", closeable, th); - return completed(); + return true; }).concat(closeAsync); - closeAsyncGracefully = closeable.closeAsyncGracefully().onErrorResume(th -> { + closeAsyncGracefully = closeable.closeAsyncGracefully().onErrorComplete(th -> { //TODO: This should use prependDelayError when available. LOGGER.debug("Ignored failure to close {}.", closeable, th); - return completed(); + return true; }).concat(closeAsyncGracefully); } } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java index a1f113d35b..5d59f6e9f9 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java @@ -223,6 +223,314 @@ public final Publisher scanWith(Supplier(this, mapperSupplier, executor); } + /** + * Transform errors emitted on this {@link Publisher} into a {@link Subscriber#onComplete()} signal + * (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         // ignored
+     *     }
+     *     return results;
+     * }
+ * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} into a + * {@link Subscriber#onComplete()} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Publisher onErrorComplete() { + return onErrorComplete(t -> true); + } + + /** + * Transform errors emitted on this {@link Publisher} which match {@code type} into a + * {@link Subscriber#onComplete()} signal (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (!type.isInstance(cause)) {
+     *           throw cause;
+     *         }
+     *     }
+     *     return results;
+     * }
+ * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param The {@link Throwable} type. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} which match {@code type} + * into a {@link Subscriber#onComplete()} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Publisher onErrorComplete(Class type) { + return onErrorComplete(type::isInstance); + } + + /** + * Transform errors emitted on this {@link Publisher} which match {@code predicate} into a + * {@link Subscriber#onComplete()} signal (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (!predicate.test(cause)) {
+     *           throw cause;
+     *         }
+     *     }
+     *     return results;
+     * }
+ * @param predicate returns {@code true} if the {@link Throwable} should be transformed to and + * {@link Subscriber#onComplete()} signal. Returns {@code false} to propagate the error. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} which match + * {@code predicate} into a {@link Subscriber#onComplete()} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Publisher onErrorComplete(Predicate predicate) { + return onErrorResume(predicate, t -> Publisher.empty()); + } + + /** + * Transform errors emitted on this {@link Publisher} into {@link Subscriber#onNext(Object)} then + * {@link Subscriber#onComplete()} signals (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         results.add(itemSupplier.apply(cause));
+     *     }
+     *     return results;
+     * }
+ * @param itemSupplier returns the element to emit to {@link Subscriber#onNext(Object)}. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} into + * {@link Subscriber#onNext(Object)} then {@link Subscriber#onComplete()} signals (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Publisher onErrorReturn(Function itemSupplier) { + return onErrorReturn(t -> true, itemSupplier); + } + + /** + * Transform errors emitted on this {@link Publisher} which match {@code type} into + * {@link Subscriber#onNext(Object)} then {@link Subscriber#onComplete()} signals (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (!type.isInstance(cause)) {
+     *           throw cause;
+     *         }
+     *         results.add(itemSupplier.apply(cause));
+     *     }
+     *     return results;
+     * }
+ * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param itemSupplier returns the element to emit to {@link Subscriber#onNext(Object)}. + * @param The type of {@link Throwable} to transform. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} into + * {@link Subscriber#onNext(Object)} then {@link Subscriber#onComplete()} signals (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Publisher onErrorReturn( + Class type, Function itemSupplier) { + @SuppressWarnings("unchecked") + final Function rawSupplier = (Function) itemSupplier; + return onErrorReturn(type::isInstance, rawSupplier); + } + + /** + * Transform errors emitted on this {@link Publisher} which match {@code predicate} into + * {@link Subscriber#onNext(Object)} then {@link Subscriber#onComplete()} signals (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (!predicate.test(cause)) {
+     *           throw cause;
+     *         }
+     *         results.add(itemSupplier.apply(cause));
+     *     }
+     *     return result;
+     * }
+ * @param predicate returns {@code true} if the {@link Throwable} should be transformed to + * {@link Subscriber#onNext(Object)} then {@link Subscriber#onComplete()} signals. Returns {@code false} to + * propagate the error. + * @param itemSupplier returns the element to emit to {@link Subscriber#onNext(Object)}. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} into + * {@link Subscriber#onNext(Object)} then {@link Subscriber#onComplete()} signals (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Publisher onErrorReturn(Predicate predicate, + Function itemSupplier) { + requireNonNull(itemSupplier); + return onErrorResume(predicate, t -> Publisher.from(itemSupplier.apply(t))); + } + + /** + * Transform errors emitted on this {@link Publisher} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         throw mapper.apply(cause);
+     *     }
+     *     return results;
+     * }
+ * @param mapper returns the error used to terminate the returned {@link Publisher}. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} into a different error. + * @see ReactiveX catch operator. + */ + public final Publisher onErrorMap(Function mapper) { + return onErrorMap(t -> true, mapper); + } + + /** + * Transform errors emitted on this {@link Publisher} which match {@code type} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (type.isInstance(cause)) {
+     *           throw mapper.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     *     return results;
+     * }
+ * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param mapper returns the error used to terminate the returned {@link Publisher}. + * @param The type of {@link Throwable} to transform. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} into a different error. + * @see ReactiveX catch operator. + */ + public final Publisher onErrorMap( + Class type, Function mapper) { + @SuppressWarnings("unchecked") + final Function rawMapper = (Function) mapper; + return onErrorMap(type::isInstance, rawMapper); + } + + /** + * Transform errors emitted on this {@link Publisher} which match {@code predicate} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     List results = resultOfThisPublisher();
+     *     try {
+     *         terminalOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (predicate.test(cause)) {
+     *           throw mapper.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     *     return results;
+     * }
+ * @param predicate returns {@code true} if the {@link Throwable} should be transformed via {@code mapper}. Returns + * {@code false} to propagate the original error. + * @param mapper returns the error used to terminate the returned {@link Publisher}. + * @return A {@link Publisher} which transform errors emitted on this {@link Publisher} into a different error. + * @see ReactiveX catch operator. + */ + public final Publisher onErrorMap(Predicate predicate, + Function mapper) { + requireNonNull(mapper); + return onErrorResume(predicate, t -> Publisher.failed(mapper.apply(t))); + } + + /** + * Recover from errors emitted by this {@link Publisher} which match {@code type} by using another {@link Publisher} + * provided by the passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     List results;
+     *     try {
+     *         results = resultOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (type.isInstance(cause)) {
+     *           // Note that nextFactory returning a error Publisher is like re-throwing (nextFactory shouldn't throw).
+     *           results = nextFactory.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     *     return results;
+     * }
+ * + * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param nextFactory Returns the next {@link Publisher}, when this {@link Publisher} emits an error. + * @param The type of {@link Throwable} to transform. + * @return A {@link Publisher} that recovers from an error from this {@link Publisher} by using another + * {@link Publisher} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Publisher onErrorResume( + Class type, Function> nextFactory) { + @SuppressWarnings("unchecked") + Function> rawNextFactory = + (Function>) nextFactory; + return onErrorResume(type::isInstance, rawNextFactory); + } + + /** + * Recover from errors emitted by this {@link Publisher} which match {@code predicate} by using another + * {@link Publisher} provided by the passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     List results;
+     *     try {
+     *         results = resultOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         if (predicate.test(cause)) {
+     *           // Note that nextFactory returning a error Publisher is like re-throwing (nextFactory shouldn't throw).
+     *           results = nextFactory.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     *     return results;
+     * }
+ * + * @param predicate returns {@code true} if the {@link Throwable} should be transformed via {@code nextFactory}. + * Returns {@code false} to propagate the original error. + * @param nextFactory Returns the next {@link Publisher}, when this {@link Publisher} emits an error. + * @return A {@link Publisher} that recovers from an error from this {@link Publisher} by using another + * {@link Publisher} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Publisher onErrorResume(Predicate predicate, + Function> nextFactory) { + requireNonNull(predicate); + requireNonNull(nextFactory); + return onErrorResume(t -> predicate.test(t) ? nextFactory.apply(t) : Publisher.failed(t)); + } + /** * Recover from any error emitted by this {@link Publisher} by using another {@link Publisher} provided by the * passed {@code nextFactory}. @@ -240,12 +548,38 @@ public final Publisher scanWith(Supplier * * @param nextFactory Returns the next {@link Publisher}, when this {@link Publisher} emits an error. + * @return A {@link Publisher} that recovers from an error from this {@link Publisher} by using another + * {@link Publisher} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Publisher onErrorResume(Function> nextFactory) { + return new ResumePublisher<>(this, nextFactory, executor); + } + + /** + * Recover from any error emitted by this {@link Publisher} by using another {@link Publisher} provided by the + * passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     List results;
+     *     try {
+     *         results = resultOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         // Note that nextFactory returning a error Publisher is like re-throwing (nextFactory shouldn't throw).
+     *         results = nextFactory.apply(cause);
+     *     }
+     *     return results;
+     * }
+ * @deprecated Use {@link #onErrorResume(Function)}. + * @param nextFactory Returns the next {@link Publisher}, when this {@link Publisher} emits an error. * @return A {@link Publisher} that recovers from an error from this {@code Publisher} by using another * {@link Publisher} provided by the passed {@code nextFactory}. * @see ReactiveX catch operator. */ + @Deprecated public final Publisher recoverWith(Function> nextFactory) { - return new ResumePublisher<>(this, nextFactory, executor); + return onErrorResume(nextFactory); } /** diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeCompletable.java index 6d97ef7795..db524c44da 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeCompletable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeCompletable.java @@ -29,9 +29,9 @@ */ final class ResumeCompletable extends AbstractNoHandleSubscribeCompletable { private final Completable original; - private final Function nextFactory; + private final Function nextFactory; - ResumeCompletable(Completable original, Function nextFactory, + ResumeCompletable(Completable original, Function nextFactory, Executor executor) { super(executor); this.original = original; @@ -53,9 +53,9 @@ private static final class ResumeSubscriber implements Subscriber { @Nullable private SequentialCancellable sequentialCancellable; @Nullable - private Function nextFactory; + private Function nextFactory; - ResumeSubscriber(Subscriber subscriber, Function nextFactory, + ResumeSubscriber(Subscriber subscriber, Function nextFactory, SignalOffloader signalOffloader, AsyncContextMap contextMap, AsyncContextProvider contextProvider) { this.subscriber = subscriber; diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumePublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumePublisher.java index ef64d926ec..046f79413f 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumePublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumePublisher.java @@ -30,9 +30,9 @@ final class ResumePublisher extends AbstractNoHandleSubscribePublisher { private final Publisher original; - private final Function> nextFactory; + private final Function> nextFactory; - ResumePublisher(Publisher original, Function> nextFactory, + ResumePublisher(Publisher original, Function> nextFactory, Executor executor) { super(executor); this.original = original; @@ -55,10 +55,10 @@ private static final class ResumeSubscriber implements Subscriber { @Nullable private SequentialSubscription sequentialSubscription; @Nullable - private Function> nextFactory; + private Function> nextFactory; ResumeSubscriber(Subscriber subscriber, - Function> nextFactory, + Function> nextFactory, SignalOffloader signalOffloader, AsyncContextMap contextMap, AsyncContextProvider contextProvider) { this.subscriber = subscriber; diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeSingle.java index 8de53901f5..c826246e29 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeSingle.java @@ -30,7 +30,7 @@ final class ResumeSingle extends AbstractNoHandleSubscribeSingle { private final Single original; - private final Function> nextFactory; + private final Function> nextFactory; /** * New instance. @@ -38,7 +38,7 @@ final class ResumeSingle extends AbstractNoHandleSubscribeSingle { * @param original Source. * @param nextFactory For creating the next {@link Single}. */ - ResumeSingle(Single original, Function> nextFactory, + ResumeSingle(Single original, Function> nextFactory, Executor executor) { super(executor); this.original = original; @@ -60,10 +60,10 @@ private static final class ResumeSubscriber implements Subscriber { @Nullable private SequentialCancellable sequentialCancellable; @Nullable - private Function> nextFactory; + private Function> nextFactory; ResumeSubscriber(Subscriber subscriber, - Function> nextFactory, + Function> nextFactory, SignalOffloader signalOffloader, AsyncContextMap contextMap, AsyncContextProvider contextProvider) { this.subscriber = subscriber; diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java index 878cb9c1bb..0b24f1cfad 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java @@ -37,6 +37,7 @@ import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.IntPredicate; +import java.util.function.Predicate; import java.util.function.Supplier; import javax.annotation.Nullable; @@ -123,6 +124,239 @@ public final Single map(Function mapper) { return new MapSingle<>(this, mapper, executor); } + /** + * Transform errors emitted on this {@link Single} into {@link Subscriber#onSuccess(Object)} signal + * (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     T result = resultOfThisSingle();
+     *     try {
+     *         terminalOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         return itemSupplier.apply(cause);
+     *     }
+     *     return result;
+     * }
+ * @param itemSupplier returns the element to emit to {@link Subscriber#onSuccess(Object)}. + * @return A {@link Single} which transform errors emitted on this {@link Single} into + * {@link Subscriber#onSuccess(Object)} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Single onErrorReturn(Function itemSupplier) { + return onErrorReturn(t -> true, itemSupplier); + } + + /** + * Transform errors emitted on this {@link Single} which match {@code type} into + * {@link Subscriber#onSuccess(Object)} signal (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     T result = resultOfThisSingle();
+     *     try {
+     *         terminalOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         if (!type.isInstance(cause)) {
+     *           throw cause;
+     *         }
+     *         return itemSupplier.apply(cause);
+     *     }
+     *     return result;
+     * }
+ * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param itemSupplier returns the element to emit to {@link Subscriber#onSuccess(Object)}. + * @param The type of {@link Throwable} to transform. + * @return A {@link Single} which transform errors emitted on this {@link Single} into + * {@link Subscriber#onSuccess(Object)} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Single onErrorReturn( + Class type, Function itemSupplier) { + @SuppressWarnings("unchecked") + final Function rawSupplier = (Function) itemSupplier; + return onErrorReturn(type::isInstance, rawSupplier); + } + + /** + * Transform errors emitted on this {@link Single} which match {@code predicate} into + * {@link Subscriber#onSuccess(Object)} signal (e.g. swallows the error). + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     T result = resultOfThisSingle();
+     *     try {
+     *         terminalOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         if (!predicate.test(cause)) {
+     *           throw cause;
+     *         }
+     *         return itemSupplier.apply(cause);
+     *     }
+     *     return result;
+     * }
+ * @param predicate returns {@code true} if the {@link Throwable} should be transformed to + * {@link Subscriber#onSuccess(Object)} signal. Returns {@code false} to propagate the error. + * @param itemSupplier returns the element to emit to {@link Subscriber#onSuccess(Object)}. + * @return A {@link Single} which transform errors emitted on this {@link Single} into + * {@link Subscriber#onSuccess(Object)} signal (e.g. swallows the error). + * @see ReactiveX catch operator. + */ + public final Single onErrorReturn(Predicate predicate, + Function itemSupplier) { + requireNonNull(itemSupplier); + return onErrorResume(predicate, t -> Single.succeeded(itemSupplier.apply(t))); + } + + /** + * Transform errors emitted on this {@link Single} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     T result = resultOfThisSingle();
+     *     try {
+     *         terminalOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         throw mapper.apply(cause);
+     *     }
+     *     return result;
+     * }
+ * @param mapper returns the error used to terminate the returned {@link Single}. + * @return A {@link Single} which transform errors emitted on this {@link Single} into a different error. + * @see ReactiveX catch operator. + */ + public final Single onErrorMap(Function mapper) { + return onErrorMap(t -> true, mapper); + } + + /** + * Transform errors emitted on this {@link Single} which match {@code type} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     T result = resultOfThisSingle();
+     *     try {
+     *         terminalOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         if (type.isInstance(cause)) {
+     *           throw mapper.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     *     return result;
+     * }
+ * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param mapper returns the error used to terminate the returned {@link Single}. + * @param The type of {@link Throwable} to transform. + * @return A {@link Single} which transform errors emitted on this {@link Single} into a different error. + * @see ReactiveX catch operator. + */ + public final Single onErrorMap( + Class type, Function mapper) { + @SuppressWarnings("unchecked") + final Function rawMapper = (Function) mapper; + return onErrorMap(type::isInstance, rawMapper); + } + + /** + * Transform errors emitted on this {@link Single} which match {@code predicate} into a different error. + *

+ * This method provides a data transformation in sequential programming similar to: + *

{@code
+     *     T results = resultOfThisSingle();
+     *     try {
+     *         terminalOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         if (predicate.test(cause)) {
+     *           throw mapper.apply(cause);
+     *         } else {
+     *           throw cause;
+     *         }
+     *     }
+     *     return result;
+     * }
+ * @param predicate returns {@code true} if the {@link Throwable} should be transformed via {@code mapper}. Returns + * {@code false} to propagate the original error. + * @param mapper returns the error used to terminate the returned {@link Single}. + * @return A {@link Single} which transform errors emitted on this {@link Single} into a different error. + * @see ReactiveX catch operator. + */ + public final Single onErrorMap(Predicate predicate, + Function mapper) { + requireNonNull(mapper); + return onErrorResume(predicate, t -> Single.failed(mapper.apply(t))); + } + + /** + * Recover from errors emitted by this {@link Single} which match {@code type} by using another {@link Single} + * provided by the passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     T result;
+     *     try {
+     *         result = resultOfThisSingle();
+     *     } catch (Throwable cause) {
+     *       if (type.isInstance(cause)) {
+     *         // Note that nextFactory returning a error Single is like re-throwing (nextFactory shouldn't throw).
+     *         result = nextFactory.apply(cause);
+     *       } else {
+     *           throw cause;
+     *       }
+     *     }
+     *     return result;
+     * }
+ * + * @param type The {@link Throwable} type to filter, operator will not apply for errors which don't match this type. + * @param nextFactory Returns the next {@link Single}, when this {@link Single} emits an error. + * @param The type of {@link Throwable} to transform. + * @return A {@link Single} that recovers from an error from this {@link Single} by using another + * {@link Single} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Single onErrorResume( + Class type, Function> nextFactory) { + @SuppressWarnings("unchecked") + Function> rawNextFactory = + (Function>) nextFactory; + return onErrorResume(type::isInstance, rawNextFactory); + } + + /** + * Recover from errors emitted by this {@link Single} which match {@code predicate} by using another + * {@link Single} provided by the passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     T result;
+     *     try {
+     *         result = resultOfThisSingle();
+     *     } catch (Throwable cause) {
+     *       if (predicate.test(cause)) {
+     *         // Note that nextFactory returning a error Single is like re-throwing (nextFactory shouldn't throw).
+     *         result = nextFactory.apply(cause);
+     *       } else {
+     *           throw cause;
+     *       }
+     *     }
+     *     return result;
+     * }
+ * + * @param predicate returns {@code true} if the {@link Throwable} should be transformed via {@code nextFactory}. + * Returns {@code false} to propagate the original error. + * @param nextFactory Returns the next {@link Single}, when this {@link Single} emits an error. + * @return A {@link Single} that recovers from an error from this {@link Single} by using another + * {@link Single} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Single onErrorResume(Predicate predicate, + Function> nextFactory) { + requireNonNull(predicate); + requireNonNull(nextFactory); + return onErrorResume(t -> predicate.test(t) ? nextFactory.apply(t) : Single.failed(t)); + } + /** * Recover from any error emitted by this {@link Single} by using another {@link Single} provided by the * passed {@code nextFactory}. @@ -139,13 +373,38 @@ public final Single map(Function mapper) { * return result; * } * @param nextFactory Returns the next {@link Single}, when this {@link Single} emits an error. - * @return A {@link Single} that recovers from an error from this {@code Single} by using another + * @return A {@link Single} that recovers from an error from this {@link Single} by using another * {@link Single} provided by the passed {@code nextFactory}. */ - public final Single recoverWith(Function> nextFactory) { + public final Single onErrorResume(Function> nextFactory) { return new ResumeSingle<>(this, nextFactory, executor); } + /** + * Recover from any error emitted by this {@link Single} by using another {@link Single} provided by the + * passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     T result;
+     *     try {
+     *         result = resultOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         // Note that nextFactory returning a error Single is like re-throwing (nextFactory shouldn't throw).
+     *         result = nextFactory.apply(cause);
+     *     }
+     *     return result;
+     * }
+ * @deprecated Use {@link #onErrorResume(Function)}. + * @param nextFactory Returns the next {@link Single}, when this {@link Single} emits an error. + * @return A {@link Single} that recovers from an error from this {@link Single} by using another + * {@link Single} provided by the passed {@code nextFactory}. + */ + @Deprecated + public final Single recoverWith(Function> nextFactory) { + return onErrorResume(nextFactory); + } + /** * Returns a {@link Single} that mirrors emissions from the {@link Single} returned by {@code next}. * Any error emitted by this {@link Single} is forwarded to the returned {@link Single}. diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java new file mode 100644 index 0000000000..b4001a3c5d --- /dev/null +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java @@ -0,0 +1,159 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import io.servicetalk.concurrent.internal.DeliberateException; +import io.servicetalk.concurrent.test.internal.TestCompletableSubscriber; + +import org.junit.Test; + +import static io.servicetalk.concurrent.api.Completable.completed; +import static io.servicetalk.concurrent.api.Completable.failed; +import static io.servicetalk.concurrent.api.SourceAdapters.toSource; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class OnErrorCompletableTest { + private final TestCompletableSubscriber subscriber = new TestCompletableSubscriber(); + private final TestCompletable first = new TestCompletable(); + + @Test + public void onErrorComplete() { + toSource(first.onErrorComplete()).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorCompleteClassMatch() { + toSource(first.onErrorComplete(DeliberateException.class)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorCompleteClassNoMatch() { + toSource(first.onErrorComplete(IllegalArgumentException.class)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorCompletePredicateMatch() { + toSource(first.onErrorComplete(t -> t == DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorCompletePredicateNoMatch() { + toSource(first.onErrorComplete(t -> false)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapMatch() { + toSource(first.onErrorMap(t -> DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapMatchThrows() { + toSource(first.onErrorMap(t -> { + throw DELIBERATE_EXCEPTION; + })).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapClassMatch() { + toSource(first.onErrorMap(DeliberateException.class, t -> DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapClassNoMatch() { + toSource(first.onErrorMap(IllegalArgumentException.class, t -> new DeliberateException())) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapPredicateMatch() { + toSource(first.onErrorMap(t -> t instanceof DeliberateException, t -> DELIBERATE_EXCEPTION)) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapPredicateNoMatch() { + toSource(first.onErrorMap(t -> false, t -> new IllegalStateException())).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumeClassMatch() { + toSource(first.onErrorResume(DeliberateException.class, t -> failed(DELIBERATE_EXCEPTION))) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumeClassNoMatch() { + toSource(first.onErrorResume(IllegalArgumentException.class, t -> completed())).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumePredicateMatch() { + toSource(first.onErrorResume(t -> t instanceof DeliberateException, + t -> failed(DELIBERATE_EXCEPTION))).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumePredicateNoMatch() { + toSource(first.onErrorResume(t -> false, t -> completed())).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } +} diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java new file mode 100644 index 0000000000..d9970a013f --- /dev/null +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java @@ -0,0 +1,212 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import io.servicetalk.concurrent.internal.DeliberateException; +import io.servicetalk.concurrent.test.internal.TestPublisherSubscriber; + +import org.junit.Test; + +import static io.servicetalk.concurrent.api.Publisher.empty; +import static io.servicetalk.concurrent.api.Publisher.failed; +import static io.servicetalk.concurrent.api.SourceAdapters.toSource; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class OnErrorPublisherTest { + private final TestPublisherSubscriber subscriber = new TestPublisherSubscriber<>(); + private final TestPublisher first = new TestPublisher<>(); + + @Test + public void onErrorComplete() { + toSource(first.onErrorComplete()).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorCompleteClassMatch() { + toSource(first.onErrorComplete(DeliberateException.class)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorCompleteClassNoMatch() { + toSource(first.onErrorComplete(IllegalArgumentException.class)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorCompletePredicateMatch() { + toSource(first.onErrorComplete(t -> t == DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorCompletePredicateNoMatch() { + toSource(first.onErrorComplete(t -> false)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorReturnMatch() { + toSource(first.onErrorReturn(t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription().request(1); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.takeOnNext(), is(1)); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorReturnThrows() { + toSource(first.onErrorReturn(t -> { + throw DELIBERATE_EXCEPTION; + })).subscribe(subscriber); + subscriber.awaitSubscription().request(1); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorReturnClassMatch() { + toSource(first.onErrorReturn(DeliberateException.class, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription().request(1); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.takeOnNext(), is(1)); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorReturnClassNoMatch() { + toSource(first.onErrorReturn(IllegalArgumentException.class, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription().request(1); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorReturnPredicateMatch() { + toSource(first.onErrorReturn(t -> t == DELIBERATE_EXCEPTION, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription().request(1); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.takeOnNext(), is(1)); + subscriber.awaitOnComplete(); + } + + @Test + public void onErrorReturnPredicateNoMatch() { + toSource(first.onErrorReturn(t -> false, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription().request(1); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapMatch() { + toSource(first.onErrorMap(t -> DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapMatchThrows() { + toSource(first.onErrorMap(t -> { + throw DELIBERATE_EXCEPTION; + })).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapClassMatch() { + toSource(first.onErrorMap(DeliberateException.class, t -> DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapClassNoMatch() { + toSource(first.onErrorMap(IllegalArgumentException.class, t -> new DeliberateException())) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapPredicateMatch() { + toSource(first.onErrorMap(t -> t instanceof DeliberateException, t -> DELIBERATE_EXCEPTION)) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapPredicateNoMatch() { + toSource(first.onErrorMap(t -> false, t -> new IllegalStateException())).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumeClassMatch() { + toSource(first.onErrorResume(DeliberateException.class, t -> failed(DELIBERATE_EXCEPTION))) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumeClassNoMatch() { + toSource(first.onErrorResume(IllegalArgumentException.class, t -> empty())).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumePredicateMatch() { + toSource(first.onErrorResume(t -> t instanceof DeliberateException, + t -> failed(DELIBERATE_EXCEPTION))).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumePredicateNoMatch() { + toSource(first.onErrorResume(t -> false, t -> empty())).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } +} diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumePublisherTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java similarity index 88% rename from servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumePublisherTest.java rename to servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java index 771c032786..e56f11af31 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumePublisherTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class ResumePublisherTest { +public final class OnErrorResumePublisherTest { private final TestPublisherSubscriber subscriber = new TestPublisherSubscriber<>(); private TestPublisher first = new TestPublisher<>(); @@ -40,7 +40,7 @@ public final class ResumePublisherTest { @Test public void testFirstComplete() { - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); subscriber.awaitSubscription().request(1); first.onNext(1); first.onComplete(); @@ -50,7 +50,7 @@ public void testFirstComplete() { @Test public void testFirstErrorSecondComplete() { - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); subscriber.awaitSubscription().request(1); first.onError(DELIBERATE_EXCEPTION); assertThat(subscriber.pollOnNext(10, MILLISECONDS), is(nullValue())); @@ -63,7 +63,7 @@ public void testFirstErrorSecondComplete() { @Test public void testFirstErrorSecondError() { - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); subscriber.awaitSubscription().request(1); first.onError(new DeliberateException()); assertThat(subscriber.pollOnNext(10, MILLISECONDS), is(nullValue())); @@ -74,7 +74,7 @@ public void testFirstErrorSecondError() { @Test public void testCancelFirstActive() { - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); final TestSubscription subscription = new TestSubscription(); first.onSubscribe(subscription); subscriber.awaitSubscription().request(1); @@ -86,7 +86,7 @@ public void testCancelFirstActive() { @Test public void testCancelSecondActive() { - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); final TestSubscription subscription = new TestSubscription(); subscriber.awaitSubscription().request(1); first.onError(DELIBERATE_EXCEPTION); @@ -100,7 +100,7 @@ public void testCancelSecondActive() { @Test public void testDemandAcrossPublishers() { - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); subscriber.awaitSubscription().request(2); first.onNext(1); first.onError(DELIBERATE_EXCEPTION); @@ -114,7 +114,7 @@ public void testDemandAcrossPublishers() { @Test public void testDuplicateOnError() { - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); subscriber.awaitSubscription().request(1); first.onError(DELIBERATE_EXCEPTION); assertThat(subscriber.pollOnNext(10, MILLISECONDS), is(nullValue())); @@ -128,7 +128,7 @@ public void testDuplicateOnError() { @Test public void exceptionInTerminalCallsOnError() { DeliberateException ex = new DeliberateException(); - toSource(first.recoverWith(throwable -> { + toSource(first.onErrorResume(throwable -> { throw ex; })).subscribe(subscriber); subscriber.awaitSubscription().request(1); @@ -140,7 +140,7 @@ public void exceptionInTerminalCallsOnError() { @Test public void nullInTerminalCallsOnError() { - toSource(first.recoverWith(throwable -> null)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> null)).subscribe(subscriber); subscriber.awaitSubscription().request(1); first.onError(DELIBERATE_EXCEPTION); assertThat(subscriber.awaitOnError(), instanceOf(NullPointerException.class)); diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumeSingleTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java similarity index 95% rename from servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumeSingleTest.java rename to servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java index deba15b24e..8ea4781eda 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumeSingleTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java @@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class ResumeSingleTest { +public final class OnErrorResumeSingleTest { private TestSingleSubscriber subscriber; private TestSingle first; @@ -44,7 +44,7 @@ public void setUp() { subscriber = new TestSingleSubscriber<>(); first = new TestSingle<>(); second = new TestSingle<>(); - toSource(first.recoverWith(throwable -> second)).subscribe(subscriber); + toSource(first.onErrorResume(throwable -> second)).subscribe(subscriber); } @Test @@ -96,7 +96,7 @@ public void testErrorSuppressOriginalException() { first = new TestSingle<>(); subscriber = new TestSingleSubscriber<>(); DeliberateException ex = new DeliberateException(); - toSource(first.recoverWith(throwable -> { + toSource(first.onErrorResume(throwable -> { throw ex; })).subscribe(subscriber); diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java new file mode 100644 index 0000000000..5396cfa0ab --- /dev/null +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java @@ -0,0 +1,169 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import io.servicetalk.concurrent.internal.DeliberateException; +import io.servicetalk.concurrent.test.internal.TestSingleSubscriber; + +import org.junit.Test; + +import static io.servicetalk.concurrent.api.Single.failed; +import static io.servicetalk.concurrent.api.Single.succeeded; +import static io.servicetalk.concurrent.api.SourceAdapters.toSource; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class OnErrorSingleTest { + private final TestSingleSubscriber subscriber = new TestSingleSubscriber<>(); + private final TestSingle first = new TestSingle<>(); + + @Test + public void onErrorReturnMatch() { + toSource(first.onErrorReturn(t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnSuccess(), is(1)); + } + + @Test + public void onErrorReturnThrows() { + toSource(first.onErrorReturn(t -> { + throw DELIBERATE_EXCEPTION; + })).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorReturnClassMatch() { + toSource(first.onErrorReturn(DeliberateException.class, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnSuccess(), is(1)); + } + + @Test + public void onErrorReturnClassNoMatch() { + toSource(first.onErrorReturn(IllegalArgumentException.class, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorReturnPredicateMatch() { + toSource(first.onErrorReturn(t -> t == DELIBERATE_EXCEPTION, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnSuccess(), is(1)); + } + + @Test + public void onErrorReturnPredicateNoMatch() { + toSource(first.onErrorReturn(t -> false, t -> 1)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapMatch() { + toSource(first.onErrorMap(t -> DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapMatchThrows() { + toSource(first.onErrorMap(t -> { + throw DELIBERATE_EXCEPTION; + })).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapClassMatch() { + toSource(first.onErrorMap(DeliberateException.class, t -> DELIBERATE_EXCEPTION)).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapClassNoMatch() { + toSource(first.onErrorMap(IllegalArgumentException.class, t -> new DeliberateException())) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapPredicateMatch() { + toSource(first.onErrorMap(t -> t instanceof DeliberateException, t -> DELIBERATE_EXCEPTION)) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorMapPredicateNoMatch() { + toSource(first.onErrorMap(t -> false, t -> new IllegalStateException())).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumeClassMatch() { + toSource(first.onErrorResume(DeliberateException.class, t -> failed(DELIBERATE_EXCEPTION))) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumeClassNoMatch() { + toSource(first.onErrorResume(IllegalArgumentException.class, t -> succeeded(1))).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumePredicateMatch() { + toSource(first.onErrorResume(t -> t instanceof DeliberateException, t -> failed(DELIBERATE_EXCEPTION))) + .subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(new DeliberateException()); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } + + @Test + public void onErrorResumePredicateNoMatch() { + toSource(first.onErrorResume(t -> false, t -> succeeded(1))).subscribe(subscriber); + subscriber.awaitSubscription(); + first.onError(DELIBERATE_EXCEPTION); + assertThat(subscriber.awaitOnError(), is(DELIBERATE_EXCEPTION)); + } +} diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherConcatMapIterableTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherConcatMapIterableTest.java index b511daff03..cd32077969 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherConcatMapIterableTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherConcatMapIterableTest.java @@ -97,7 +97,7 @@ public void upstreamRecoverWithMakesProgress() throws Exception { }).when(mockSubscriber).onNext(any()); Processor, List> processor = newPublisherProcessor(); - toSource(fromSource(processor).recoverWith(cause -> { + toSource(fromSource(processor).onErrorResume(cause -> { if (cause != DELIBERATE_EXCEPTION) { // recover! return from(singletonList("two")); } diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapMergeTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapMergeTest.java index 1f8fbddb06..2d083d229e 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapMergeTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapMergeTest.java @@ -124,7 +124,7 @@ public void mappedRecoverMakesProgress() throws Exception { }).when(mockSubscriber).onNext(any()); Processor processor = newPublisherProcessor(); - toSource(fromSource(processor).flatMapMergeDelayError(i -> from(i + 10).recoverWith(cause -> + toSource(fromSource(processor).flatMapMergeDelayError(i -> from(i + 10).onErrorResume(cause -> from(i + 20).concat(failed(cause))))).subscribe(mockSubscriber); latchOnSubscribe.await(); @@ -932,7 +932,7 @@ public void concurrentMappedErrorAndPublisherTermination() throws Exception { } throw new DeliberateException(); }).toPublisher(), 1024, 500) - .recoverWith(t -> { + .onErrorResume(t -> { error.set(t); return Publisher.empty(); }).collect(ArrayList::new, (ints, s) -> { diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapSingleTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapSingleTest.java index 08c595f495..2b35a4cea8 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapSingleTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/PublisherFlatMapSingleTest.java @@ -104,7 +104,7 @@ public void concurrentSingleErrorAndPublisherTermination() throws Exception { return x; } throw new DeliberateException(); - }), 1024, 500).recoverWith(t -> { + }), 1024, 500).onErrorResume(t -> { error.set(t); return Publisher.empty(); }).collect(ArrayList::new, (ints, s) -> { diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteClassTckTest.java new file mode 100644 index 0000000000..a414a9e959 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteClassTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Completable; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class CompletableOnErrorCompleteClassTckTest extends AbstractCompletableOperatorTckTest { + @Override + protected Completable composeCompletable(final Completable completable) { + return completable.onErrorComplete(DeliberateException.class); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompletePredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompletePredicateTckTest.java new file mode 100644 index 0000000000..6ced22d987 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompletePredicateTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Completable; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class CompletableOnErrorCompletePredicateTckTest extends AbstractCompletableOperatorTckTest { + @Override + protected Completable composeCompletable(final Completable completable) { + return completable.onErrorComplete(t -> t instanceof DeliberateException); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java new file mode 100644 index 0000000000..58508e80a2 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Completable; + +import org.testng.annotations.Test; + +@Test +public class CompletableOnErrorCompleteTckTest extends AbstractCompletableOperatorTckTest { + @Override + protected Completable composeCompletable(final Completable completable) { + return completable.onErrorComplete(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapClassTckTest.java new file mode 100644 index 0000000000..a02d5a6f46 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapClassTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Completable; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class CompletableOnErrorMapClassTckTest extends AbstractCompletableOperatorTckTest { + @Override + protected Completable composeCompletable(final Completable completable) { + return completable.onErrorMap(DeliberateException.class, t -> t); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapPredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapPredicateTckTest.java new file mode 100644 index 0000000000..0b8d56deb2 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapPredicateTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Completable; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class CompletableOnErrorMapPredicateTckTest extends AbstractCompletableOperatorTckTest { + @Override + protected Completable composeCompletable(final Completable completable) { + return completable.onErrorMap(t -> t instanceof DeliberateException, t -> t); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapTckTest.java new file mode 100644 index 0000000000..3166b3a2ac --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorMapTckTest.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Completable; + +import org.testng.annotations.Test; + +@Test +public class CompletableOnErrorMapTckTest extends AbstractCompletableOperatorTckTest { + @Override + protected Completable composeCompletable(final Completable completable) { + return completable.onErrorMap(t -> t); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeClassTckTest.java new file mode 100644 index 0000000000..360242e619 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeClassTckTest.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +import static io.servicetalk.concurrent.api.Completable.completed; +import static io.servicetalk.concurrent.api.Completable.failed; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; + +@Test +public class CompletableOnErrorResumeClassTckTest extends AbstractCompletableTckTest { + @Override + public Publisher createServiceTalkPublisher(long elements) { + return failed(DELIBERATE_EXCEPTION) + .onErrorResume(DeliberateException.class, cause -> completed()).toPublisher(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumePredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumePredicateTckTest.java new file mode 100644 index 0000000000..1b0ee9233f --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumePredicateTckTest.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +import static io.servicetalk.concurrent.api.Completable.completed; +import static io.servicetalk.concurrent.api.Completable.failed; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; + +@Test +public class CompletableOnErrorResumePredicateTckTest extends AbstractCompletableTckTest { + @Override + public Publisher createServiceTalkPublisher(long elements) { + return failed(DELIBERATE_EXCEPTION) + .onErrorResume(t -> t instanceof DeliberateException, cause -> completed()).toPublisher(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeTckTest.java index 96985ac9ad..3cf620f9f2 100644 --- a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeTckTest.java +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorResumeTckTest.java @@ -15,17 +15,18 @@ */ package io.servicetalk.concurrent.reactivestreams.tck; -import io.servicetalk.concurrent.api.Completable; import io.servicetalk.concurrent.api.Publisher; -import io.servicetalk.concurrent.internal.DeliberateException; import org.testng.annotations.Test; +import static io.servicetalk.concurrent.api.Completable.completed; +import static io.servicetalk.concurrent.api.Completable.failed; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; + @Test public class CompletableOnErrorResumeTckTest extends AbstractCompletableTckTest { @Override public Publisher createServiceTalkPublisher(long elements) { - return Completable.failed(DeliberateException.DELIBERATE_EXCEPTION) - .onErrorResume(cause -> Completable.completed()).toPublisher(); + return failed(DELIBERATE_EXCEPTION).onErrorResume(cause -> completed()).toPublisher(); } } diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteClassTckTest.java new file mode 100644 index 0000000000..ccffe94a4d --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteClassTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorCompleteClassTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorComplete(DeliberateException.class); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompletePredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompletePredicateTckTest.java new file mode 100644 index 0000000000..86925c7905 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompletePredicateTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorCompletePredicateTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorComplete(t -> t instanceof DeliberateException); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java new file mode 100644 index 0000000000..6242b261f5 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorCompleteTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorComplete(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapClassTckTest.java new file mode 100644 index 0000000000..a3188c5ad3 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapClassTckTest.java @@ -0,0 +1,31 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +import static java.util.function.Function.identity; + +@Test +public class PublisherOnErrorMapClassTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorMap(DeliberateException.class, identity()); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapPredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapPredicateTckTest.java new file mode 100644 index 0000000000..b444a6737b --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapPredicateTckTest.java @@ -0,0 +1,31 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +import static java.util.function.Function.identity; + +@Test +public class PublisherOnErrorMapPredicateTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorMap(t -> t instanceof DeliberateException, identity()); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapTckTest.java new file mode 100644 index 0000000000..35cf9aa613 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorMapTckTest.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; + +import org.testng.annotations.Test; + +import static java.util.function.Function.identity; + +@Test +public class PublisherOnErrorMapTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorMap(identity()); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeClassTckTest.java new file mode 100644 index 0000000000..083da02b7c --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeClassTckTest.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorResumeClassTckTest extends AbstractPublisherTckTest { + @Override + public Publisher createServiceTalkPublisher(long elements) { + int numElements = TckUtils.requestNToInt(elements); + + return TckUtils.newFailedPublisher() + .onErrorResume(DeliberateException.class, t -> TckUtils.newPublisher(numElements)); + } + + @Override + public long maxElementsFromPublisher() { + return TckUtils.maxElementsFromPublisher(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumePredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumePredicateTckTest.java new file mode 100644 index 0000000000..50202bb6f0 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumePredicateTckTest.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorResumePredicateTckTest extends AbstractPublisherTckTest { + @Override + public Publisher createServiceTalkPublisher(long elements) { + int numElements = TckUtils.requestNToInt(elements); + + return TckUtils.newFailedPublisher() + .onErrorResume(t -> t instanceof DeliberateException, t -> TckUtils.newPublisher(numElements)); + } + + @Override + public long maxElementsFromPublisher() { + return TckUtils.maxElementsFromPublisher(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeTckTest.java index d3244a2ea7..8e3b91da67 100644 --- a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeTckTest.java +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorResumeTckTest.java @@ -27,7 +27,7 @@ public Publisher createServiceTalkPublisher(long elements) { int numElements = TckUtils.requestNToInt(elements); return TckUtils.newFailedPublisher() - .recoverWith(cause -> TckUtils.newPublisher(numElements)); + .onErrorResume(cause -> TckUtils.newPublisher(numElements)); } @Override diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnClassTckTest.java new file mode 100644 index 0000000000..61ef87d833 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnClassTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorReturnClassTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorReturn(DeliberateException.class, t -> 1); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnPredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnPredicateTckTest.java new file mode 100644 index 0000000000..192156782a --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnPredicateTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorReturnPredicateTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorReturn(t -> t instanceof DeliberateException, t -> 1); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnTckTest.java new file mode 100644 index 0000000000..66100c39ae --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorReturnTckTest.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; + +import org.testng.annotations.Test; + +@Test +public class PublisherOnErrorReturnTckTest extends AbstractPublisherOperatorTckTest { + @Override + protected Publisher composePublisher(final Publisher publisher, final int elements) { + return publisher.onErrorReturn(t -> 1); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapClassTckTest.java new file mode 100644 index 0000000000..6424f2488e --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapClassTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Single; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class SingleOnErrorMapClassTckTest extends AbstractSingleOperatorTckTest { + @Override + protected Single composeSingle(final Single single) { + return single.onErrorMap(DeliberateException.class, t -> t); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapPredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapPredicateTckTest.java new file mode 100644 index 0000000000..23540ec777 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapPredicateTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Single; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class SingleOnErrorMapPredicateTckTest extends AbstractSingleOperatorTckTest { + @Override + protected Single composeSingle(final Single single) { + return single.onErrorMap(t -> t instanceof DeliberateException, t -> t); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapTckTest.java new file mode 100644 index 0000000000..d7f7e3417a --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorMapTckTest.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Single; + +import org.testng.annotations.Test; + +@Test +public class SingleOnErrorMapTckTest extends AbstractSingleOperatorTckTest { + @Override + protected Single composeSingle(final Single single) { + return single.onErrorMap(t -> t); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeClassTckTest.java new file mode 100644 index 0000000000..4527593567 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeClassTckTest.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2018 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.api.Single; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +import static io.servicetalk.concurrent.api.Single.succeeded; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; + +@Test +public class SingleOnErrorResumeClassTckTest extends AbstractSingleTckTest { + @Override + public Publisher createServiceTalkPublisher(long elements) { + return Single.failed(DELIBERATE_EXCEPTION) + .onErrorResume(DeliberateException.class, cause -> succeeded(1)).toPublisher(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumePredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumePredicateTckTest.java new file mode 100644 index 0000000000..2867f9e59a --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumePredicateTckTest.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2018 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Publisher; +import io.servicetalk.concurrent.api.Single; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +import static io.servicetalk.concurrent.api.Single.succeeded; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; + +@Test +public class SingleOnErrorResumePredicateTckTest extends AbstractSingleTckTest { + @Override + public Publisher createServiceTalkPublisher(long elements) { + return Single.failed(DELIBERATE_EXCEPTION) + .onErrorResume(t -> t instanceof DeliberateException, cause -> succeeded(1)).toPublisher(); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeTckTest.java index 3bbb4be509..470a9f635f 100644 --- a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeTckTest.java +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorResumeTckTest.java @@ -17,16 +17,17 @@ import io.servicetalk.concurrent.api.Publisher; import io.servicetalk.concurrent.api.Single; -import io.servicetalk.concurrent.internal.DeliberateException; import org.testng.annotations.Test; +import static io.servicetalk.concurrent.api.Single.succeeded; +import static io.servicetalk.concurrent.internal.DeliberateException.DELIBERATE_EXCEPTION; + @Test public class SingleOnErrorResumeTckTest extends AbstractSingleTckTest { - @Override public Publisher createServiceTalkPublisher(long elements) { - return Single.failed(DeliberateException.DELIBERATE_EXCEPTION) - .recoverWith(cause -> Single.succeeded(1)).toPublisher(); + return Single.failed(DELIBERATE_EXCEPTION) + .onErrorResume(cause -> succeeded(1)).toPublisher(); } } diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnClassTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnClassTckTest.java new file mode 100644 index 0000000000..d7c11a6893 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnClassTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Single; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class SingleOnErrorReturnClassTckTest extends AbstractSingleOperatorTckTest { + @Override + protected Single composeSingle(final Single single) { + return single.onErrorReturn(DeliberateException.class, t -> 1); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnPredicateTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnPredicateTckTest.java new file mode 100644 index 0000000000..f47f4cbe54 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnPredicateTckTest.java @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Single; +import io.servicetalk.concurrent.internal.DeliberateException; + +import org.testng.annotations.Test; + +@Test +public class SingleOnErrorReturnPredicateTckTest extends AbstractSingleOperatorTckTest { + @Override + protected Single composeSingle(final Single single) { + return single.onErrorReturn(t -> t instanceof DeliberateException, t -> 1); + } +} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnTckTest.java new file mode 100644 index 0000000000..00b2bfac65 --- /dev/null +++ b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/SingleOnErrorReturnTckTest.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.reactivestreams.tck; + +import io.servicetalk.concurrent.api.Single; + +import org.testng.annotations.Test; + +@Test +public class SingleOnErrorReturnTckTest extends AbstractSingleOperatorTckTest { + @Override + protected Single composeSingle(final Single single) { + return single.onErrorReturn(t -> 1); + } +} diff --git a/servicetalk-dns-discovery-netty/src/main/java/io/servicetalk/dns/discovery/netty/DefaultDnsClient.java b/servicetalk-dns-discovery-netty/src/main/java/io/servicetalk/dns/discovery/netty/DefaultDnsClient.java index 2775487e36..be53fb9dbf 100644 --- a/servicetalk-dns-discovery-netty/src/main/java/io/servicetalk/dns/discovery/netty/DefaultDnsClient.java +++ b/servicetalk-dns-discovery-netty/src/main/java/io/servicetalk/dns/discovery/netty/DefaultDnsClient.java @@ -242,7 +242,7 @@ public Publisher>> dnsSrvQu return cause == SrvAddressRemovedException.DNS_SRV_ADDR_REMOVED || aRecordMap.remove(srvEvent.address().hostName()) == null ? Completable.failed(cause) : srvHostNameRepeater.apply(i); - }).recoverWith(cause -> empty()); // retryWhen will propagate onError, but we don't want this. + }).onErrorComplete(); // retryWhen will propagate onError, but we don't want this. } else if (srvEvent instanceof SrvInactiveEvent) { // Unwrap the list so we can use it in SrvInactiveCombinerOperator below. return from(((SrvInactiveEvent) srvEvent).aggregatedEvents); @@ -755,7 +755,7 @@ private static Publisher Publisher>> recoverWithInactiveEvents( AbstractDnsPublisher pub, boolean generateAggregateEvent) { - return pub.recoverWith(cause -> { + return pub.onErrorResume(cause -> { AbstractDnsPublisher.AbstractDnsSubscription subscription = pub.subscription; if (subscription != null) { List> events = subscription.generateInactiveEvent(); diff --git a/servicetalk-encoding-netty/src/test/java/io/servicetalk/encoding/netty/NettyChannelContentCodecTest.java b/servicetalk-encoding-netty/src/test/java/io/servicetalk/encoding/netty/NettyChannelContentCodecTest.java index 236777cb89..be5641ff55 100644 --- a/servicetalk-encoding-netty/src/test/java/io/servicetalk/encoding/netty/NettyChannelContentCodecTest.java +++ b/servicetalk-encoding-netty/src/test/java/io/servicetalk/encoding/netty/NettyChannelContentCodecTest.java @@ -124,9 +124,9 @@ public void testEncodeOverTheLimitStreaming() throws Exception { final AtomicReference error = new AtomicReference<>(); codec.decode(Publisher.from(EMPTY_BUFFER, encoded), DEFAULT_ALLOCATOR) - .recoverWith(t -> { + .onErrorComplete(t -> { error.set((Exception) t); - return Publisher.empty(); + return true; }).toFuture().get(); throw error.get(); diff --git a/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/BadResponseHandlingServiceFilter.java b/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/BadResponseHandlingServiceFilter.java index 60c92941d2..5d4e0f8596 100644 --- a/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/BadResponseHandlingServiceFilter.java +++ b/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/BadResponseHandlingServiceFilter.java @@ -41,7 +41,7 @@ public StreamingHttpServiceFilter create(final StreamingHttpService service) { @Override public Single handle(HttpServiceContext ctx, StreamingHttpRequest request, StreamingHttpResponseFactory responseFactory) { - return super.handle(ctx, request, responseFactory).recoverWith(cause -> { + return super.handle(ctx, request, responseFactory).onErrorResume(cause -> { if (cause instanceof BadResponseStatusException) { // It's useful to include the exception message in the payload for demonstration purposes, but // this is not recommended in production as it may leak internal information. diff --git a/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/GatewayService.java b/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/GatewayService.java index d2c521a1bf..086c27d200 100644 --- a/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/GatewayService.java +++ b/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/GatewayService.java @@ -118,7 +118,7 @@ private Single> queryRecommendationDetails(List succeeded(new Rating(recommendation.getEntityId(), -1))); + .onErrorReturn(cause -> new Rating(recommendation.getEntityId(), -1)); // The below asynchronously queries metadata, user and rating backends and zips them into a single // FullRecommendation instance. diff --git a/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/StreamingGatewayService.java b/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/StreamingGatewayService.java index e971cbd4c0..a1d55e6575 100644 --- a/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/StreamingGatewayService.java +++ b/servicetalk-examples/http/service-composition/src/main/java/io/servicetalk/examples/http/service/composition/StreamingGatewayService.java @@ -109,10 +109,10 @@ private Publisher queryRecommendationDetails(Publisher { + .onErrorReturn(cause -> { LOGGER.error("Error querying ratings service. Ignoring and providing a fallback.", cause); - return succeeded(new Rating(recommendation.getEntityId(), -1)); + return new Rating(recommendation.getEntityId(), -1); }); // The below asynchronously queries metadata, user and rating backends and zips them into a single diff --git a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcClientBuilder.java b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcClientBuilder.java index 6f2cb7fe87..e5520c10bc 100644 --- a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcClientBuilder.java +++ b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcClientBuilder.java @@ -279,7 +279,7 @@ protected Single request(final StreamingHttpRequester del } catch (Throwable t) { return failed(toGrpcException(t)); } - return resp.recoverWith(t -> failed(toGrpcException(t))); + return resp.onErrorMap(GrpcClientBuilder::toGrpcException); } }); appendedCatchAllFilter = true; diff --git a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java index bbbb47abf1..8eabf4eab0 100644 --- a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java +++ b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java @@ -64,7 +64,6 @@ import java.util.function.Supplier; import javax.annotation.Nullable; -import static io.servicetalk.concurrent.api.Single.failed; import static io.servicetalk.concurrent.api.Single.succeeded; import static io.servicetalk.grpc.api.GrpcRouteConversions.toAsyncCloseable; import static io.servicetalk.grpc.api.GrpcRouteConversions.toRequestStreamingRoute; @@ -265,8 +264,8 @@ public Single handle(final HttpServiceContext ctx, final HttpReque .payloadBody(rawResp, serializationProvider.serializerFor(responseEncoding, responseClass))) - .recoverWith(cause -> succeeded(newErrorResponse(responseFactory, - finalServiceContext, cause, ctx.executionContext().bufferAllocator()))); + .onErrorReturn(cause -> newErrorResponse(responseFactory, + finalServiceContext, cause, ctx.executionContext().bufferAllocator())); } catch (Throwable t) { return succeeded(newErrorResponse(responseFactory, serviceContext, t, ctx.executionContext().bufferAllocator())); @@ -402,16 +401,16 @@ Builder addResponseStreamingRoute( @Override public Publisher handle(final GrpcServiceContext ctx, final Publisher request) { return request.firstOrError() - .recoverWith(t -> { + .onErrorMap(t -> { if (t instanceof NoSuchElementException) { - return failed(new GrpcStatus(INVALID_ARGUMENT, null, + return new GrpcStatus(INVALID_ARGUMENT, null, SINGLE_MESSAGE_EXPECTED_NONE_RECEIVED_MSG) - .asException()); + .asException(); } else if (t instanceof IllegalArgumentException) { - return failed(new GrpcStatus(INVALID_ARGUMENT, null, - MORE_THAN_ONE_MESSAGE_RECEIVED_MSG).asException()); + return new GrpcStatus(INVALID_ARGUMENT, null, + MORE_THAN_ONE_MESSAGE_RECEIVED_MSG).asException(); } else { - return failed(t); + return t; } }) .flatMapPublisher(rawReq -> route.handle(ctx, rawReq)); diff --git a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java index a62a59e802..803d6d1376 100644 --- a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java +++ b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java @@ -370,7 +370,7 @@ public Single handle(final HttpServiceContext ctx, } catch (Throwable cause) { return convertToGrpcErrorResponse(ctx, responseFactory, cause); } - return handle.recoverWith(cause -> convertToGrpcErrorResponse(ctx, responseFactory, cause)); + return handle.onErrorResume(cause -> convertToGrpcErrorResponse(ctx, responseFactory, cause)); } private static Single convertToGrpcErrorResponse( diff --git a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultHttpExecutionStrategy.java b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultHttpExecutionStrategy.java index e57fd099d1..20ff088aaa 100644 --- a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultHttpExecutionStrategy.java +++ b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultHttpExecutionStrategy.java @@ -26,7 +26,6 @@ import java.util.function.Function; import javax.annotation.Nullable; -import static io.servicetalk.concurrent.api.Single.succeeded; import static io.servicetalk.concurrent.api.internal.OffloaderAwareExecutor.ensureThreadAffinity; import static io.servicetalk.http.api.HttpExecutionStrategies.Builder.MergeStrategy.Merge; import static io.servicetalk.http.api.HttpExecutionStrategies.Builder.MergeStrategy.ReturnOther; @@ -106,7 +105,7 @@ public Publisher invokeService( if (offloaded(OFFLOAD_RECEIVE_META)) { final StreamingHttpRequest r = request; resp = e.submit(() -> service.apply(r).subscribeShareContext()) - .recoverWith(cause -> succeeded(errorHandler.apply(cause, e))) + .onErrorReturn(cause -> errorHandler.apply(cause, e)) // exec.submit() returns a Single>, so flatten the nested Publisher. .flatMapPublisher(identity()); } else { diff --git a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultServiceDiscoveryRetryStrategy.java b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultServiceDiscoveryRetryStrategy.java index bf1095814b..c35404b0be 100644 --- a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultServiceDiscoveryRetryStrategy.java +++ b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/DefaultServiceDiscoveryRetryStrategy.java @@ -74,7 +74,7 @@ public Publisher> apply(final Publisher> sdEvents) { } return sdEvents.map(eventsCache::consume) - .recoverWith(cause -> { + .onErrorResume(cause -> { final Collection events = eventsCache.errorSeen(); return events == null ? failed(cause) : Publisher.from(events.stream() .map(flipAvailability).collect(toList())) diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/NettyHttpServer.java b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/NettyHttpServer.java index 20be460413..5a345c5a90 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/NettyHttpServer.java +++ b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/NettyHttpServer.java @@ -86,7 +86,6 @@ import static io.servicetalk.concurrent.api.Completable.completed; import static io.servicetalk.concurrent.api.Completable.defer; import static io.servicetalk.concurrent.api.Publisher.from; -import static io.servicetalk.concurrent.api.Single.succeeded; import static io.servicetalk.concurrent.api.SourceAdapters.toSource; import static io.servicetalk.http.api.HttpHeaderNames.CONTENT_LENGTH; import static io.servicetalk.http.api.HttpHeaderValues.ZERO; @@ -338,9 +337,8 @@ public void onComplete() { Publisher responsePublisher = strategy .invokeService(executionContext().executor(), request, req -> service.handle(NettyHttpServerConnection.this, req, streamingResponseFactory()) - .recoverWith(cause -> - succeeded(newErrorResponse(cause, executionContext.executor(), - req.version(), keepAlive))) + .onErrorReturn(cause -> newErrorResponse(cause, executionContext.executor(), + req.version(), keepAlive)) .flatMapPublisher(response -> { keepAlive.addConnectionHeaderIfNecessary(response); @@ -360,7 +358,7 @@ public void onComplete() { // Discarding the request payload body is an operation which should not impact the state of // request/response processing. It's appropriate to recover from any error here. // ST may introduce RejectedSubscribeError if user already consumed the request payload body - .onErrorResume(t -> completed()))); + .onErrorComplete())); } return responsePublisher.concat(requestCompletion); diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/ProxyConnectConnectionFactoryFilter.java b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/ProxyConnectConnectionFactoryFilter.java index cfe784eda0..3340e2d032 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/ProxyConnectConnectionFactoryFilter.java +++ b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/ProxyConnectConnectionFactoryFilter.java @@ -80,7 +80,7 @@ public Single newConnection(final ResolvedAddress resolvedAddress, return c.request(defaultStrategy(), c.connect(connectAddress).addHeader(CONTENT_LENGTH, ZERO)) .flatMap(response -> handleConnectResponse(c, response)) // Close recently created connection in case of any error while it connects to the proxy: - .recoverWith(t -> c.closeAsync().concat(failed(t))); + .onErrorResume(t -> c.closeAsync().concat(failed(t))); } catch (Throwable t) { return c.closeAsync().concat(failed(t)); } diff --git a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpAuthConnectionFactoryClientTest.java b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpAuthConnectionFactoryClientTest.java index 7cc710a352..c7d7e1a9a8 100644 --- a/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpAuthConnectionFactoryClientTest.java +++ b/servicetalk-http-netty/src/test/java/io/servicetalk/http/netty/HttpAuthConnectionFactoryClientTest.java @@ -103,7 +103,7 @@ public Single newConnection( final ResolvedAddress resolvedAddress, @Nullable final TransportObserver observer) { return delegate.newConnection(resolvedAddress, observer).flatMap(cnx -> cnx.request(defaultStrategy(), newTestRequest(cnx, "/auth")) - .recoverWith(cause -> { + .onErrorResume(cause -> { cnx.closeAsync().subscribe(); return failed(new IllegalStateException("failed auth")); }) diff --git a/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/auth/BasicAuthHttpServiceFilter.java b/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/auth/BasicAuthHttpServiceFilter.java index 4efa59d69e..e44ba364b7 100644 --- a/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/auth/BasicAuthHttpServiceFilter.java +++ b/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/auth/BasicAuthHttpServiceFilter.java @@ -322,7 +322,7 @@ public Single handle(final HttpServiceContext ctx, return config.credentialsVerifier.apply(userId, password) .flatMap(userInfo -> onAuthenticated(ctx, request, factory, userInfo)) - .recoverWith(t -> { + .onErrorResume(t -> { if (t instanceof AuthenticationException) { return onAccessDenied(request, factory); } diff --git a/servicetalk-opentracing-zipkin-publisher/src/main/java/io/servicetalk/opentracing/zipkin/publisher/reporter/HttpReporter.java b/servicetalk-opentracing-zipkin-publisher/src/main/java/io/servicetalk/opentracing/zipkin/publisher/reporter/HttpReporter.java index 666c7f84d4..c7c2c3d21d 100644 --- a/servicetalk-opentracing-zipkin-publisher/src/main/java/io/servicetalk/opentracing/zipkin/publisher/reporter/HttpReporter.java +++ b/servicetalk-opentracing-zipkin-publisher/src/main/java/io/servicetalk/opentracing/zipkin/publisher/reporter/HttpReporter.java @@ -187,9 +187,9 @@ private static Function encodedSpansReporter(final HttpClie } }) .ignoreElement() - .onErrorResume(cause -> { + .onErrorComplete(cause -> { LOGGER.error("Failed to send a span, ignoring.", cause); - return completed(); + return true; }); } diff --git a/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/DefaultNettyConnection.java b/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/DefaultNettyConnection.java index d6f02d8fe2..76168b83fa 100644 --- a/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/DefaultNettyConnection.java +++ b/servicetalk-transport-netty-internal/src/main/java/io/servicetalk/transport/netty/internal/DefaultNettyConnection.java @@ -72,7 +72,6 @@ import static io.servicetalk.concurrent.api.Executors.immediate; import static io.servicetalk.concurrent.api.Processors.newCompletableProcessor; import static io.servicetalk.concurrent.api.Processors.newSingleProcessor; -import static io.servicetalk.concurrent.api.Publisher.failed; import static io.servicetalk.concurrent.api.SourceAdapters.fromSource; import static io.servicetalk.concurrent.api.SourceAdapters.toSource; import static io.servicetalk.concurrent.internal.SubscriberUtils.deliverErrorFromSource; @@ -149,7 +148,7 @@ private DefaultNettyConnection(Channel channel, BufferAllocator allocator, Execu UnaryOperator enrichProtocolError) { super(channel, executor); nettyChannelPublisher = new NettyChannelPublisher<>(channel, terminalPredicate, closeHandler); - this.readPublisher = registerReadObserver(nettyChannelPublisher.recoverWith(this::enrichErrorPublisher)); + this.readPublisher = registerReadObserver(nettyChannelPublisher.onErrorMap(this::enrichError)); this.executionContext = new DefaultExecutionContext(allocator, fromNettyEventLoop(channel.eventLoop()), executor, executionStrategy); this.closeHandler = requireNonNull(closeHandler); @@ -336,14 +335,6 @@ public void onComplete() { }); } - private Publisher enrichErrorPublisher(final Throwable t) { - return failed(enrichError(t)); - } - - private Completable enrichErrorCompletable(final Throwable t) { - return Completable.failed(enrichError(t)); - } - private Throwable enrichError(final Throwable t) { Throwable throwable; if (t instanceof AbortedFirstWrite) { @@ -403,7 +394,7 @@ protected void handleSubscribe(Subscriber completableSubscriber) { .subscribe(subscriber); } } - }).onErrorResume(this::enrichErrorCompletable); + }).onErrorMap(this::enrichError); } /** From f88793e7dc65ba08413a0fcb7dcc7d98e1d875c6 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Tue, 16 Mar 2021 06:56:14 -0700 Subject: [PATCH 2/8] add specialized onError operator implementations --- .../concurrent/api/Completable.java | 53 ++++++------ .../api/OnErrorCompleteCompletable.java | 74 ++++++++++++++++ .../api/OnErrorCompletePublisher.java | 79 +++++++++++++++++ .../concurrent/api/OnErrorMapCompletable.java | 82 ++++++++++++++++++ .../concurrent/api/OnErrorMapPublisher.java | 86 +++++++++++++++++++ .../concurrent/api/OnErrorMapSingle.java | 83 ++++++++++++++++++ ...ble.java => OnErrorResumeCompletable.java} | 51 +++++------ ...isher.java => OnErrorResumePublisher.java} | 54 ++++++------ ...meSingle.java => OnErrorResumeSingle.java} | 61 ++++++------- .../servicetalk/concurrent/api/Publisher.java | 59 ++++++------- .../io/servicetalk/concurrent/api/Single.java | 53 ++++++------ ...=> OnErrorOnErrorResumePublisherTest.java} | 2 +- ...va => OnErrorOnErrorResumeSingleTest.java} | 2 +- ...java => OnErrorResumeCompletableTest.java} | 2 +- .../CompletableOnErrorCompleteTckTest.java | 28 ------ .../tck/PublisherOnErrorCompleteTckTest.java | 28 ------ 16 files changed, 563 insertions(+), 234 deletions(-) create mode 100644 servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompleteCompletable.java create mode 100644 servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompletePublisher.java create mode 100644 servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapCompletable.java create mode 100644 servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapPublisher.java create mode 100644 servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapSingle.java rename servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/{ResumeCompletable.java => OnErrorResumeCompletable.java} (63%) rename servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/{ResumePublisher.java => OnErrorResumePublisher.java} (62%) rename servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/{ResumeSingle.java => OnErrorResumeSingle.java} (59%) rename servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/{OnErrorResumePublisherTest.java => OnErrorOnErrorResumePublisherTest.java} (99%) rename servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/{OnErrorResumeSingleTest.java => OnErrorOnErrorResumeSingleTest.java} (98%) rename servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/{ResumeCompletableTest.java => OnErrorResumeCompletableTest.java} (98%) delete mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java delete mode 100644 servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java index fe8131771e..0596b426c1 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Completable.java @@ -169,7 +169,7 @@ public final Completable onErrorComplete(Class type) { * @see ReactiveX catch operator. */ public final Completable onErrorComplete(Predicate predicate) { - return onErrorResume(predicate, t -> Completable.completed()); + return new OnErrorCompleteCompletable(this, predicate, executor); } /** @@ -242,8 +242,29 @@ public final Completable onErrorMap( */ public final Completable onErrorMap(Predicate predicate, Function mapper) { - requireNonNull(mapper); - return onErrorResume(predicate, t -> Completable.failed(mapper.apply(t))); + return new OnErrorMapCompletable(this, predicate, mapper, executor); + } + + /** + * Recover from any error emitted by this {@link Completable} by using another {@link Completable} provided by the + * passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     try {
+     *         resultOfThisCompletable();
+     *     } catch (Throwable cause) {
+     *         // Note that nextFactory returning a error Completable is like re-throwing (nextFactory shouldn't throw).
+     *         nextFactory.apply(cause);
+     *     }
+     * }
+ * + * @param nextFactory Returns the next {@link Completable}, if this {@link Completable} emits an error. + * @return A {@link Completable} that recovers from an error from this {@link Completable} by using another + * {@link Completable} provided by the passed {@code nextFactory}. + */ + public final Completable onErrorResume(Function nextFactory) { + return onErrorResume(t -> true, nextFactory); } /** @@ -306,31 +327,7 @@ public final Completable onErrorResume( */ public final Completable onErrorResume(Predicate predicate, Function nextFactory) { - requireNonNull(predicate); - requireNonNull(nextFactory); - return onErrorResume(t -> predicate.test(t) ? nextFactory.apply(t) : Completable.failed(t)); - } - - /** - * Recover from any error emitted by this {@link Completable} by using another {@link Completable} provided by the - * passed {@code nextFactory}. - *

- * This method provides similar capabilities to a try/catch block in sequential programming: - *

{@code
-     *     try {
-     *         resultOfThisCompletable();
-     *     } catch (Throwable cause) {
-     *         // Note that nextFactory returning a error Completable is like re-throwing (nextFactory shouldn't throw).
-     *         nextFactory.apply(cause);
-     *     }
-     * }
- * - * @param nextFactory Returns the next {@link Completable}, if this {@link Completable} emits an error. - * @return A {@link Completable} that recovers from an error from this {@link Completable} by using another - * {@link Completable} provided by the passed {@code nextFactory}. - */ - public final Completable onErrorResume(Function nextFactory) { - return new ResumeCompletable(this, nextFactory, executor); + return new OnErrorResumeCompletable(this, predicate, nextFactory, executor); } /** diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompleteCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompleteCompletable.java new file mode 100644 index 0000000000..935fcd3154 --- /dev/null +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompleteCompletable.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import io.servicetalk.concurrent.Cancellable; + +import java.util.function.Predicate; + +import static java.util.Objects.requireNonNull; + +final class OnErrorCompleteCompletable extends AbstractSynchronousCompletableOperator { + private final Predicate predicate; + + OnErrorCompleteCompletable(final Completable original, Predicate predicate, + final Executor executor) { + super(original, executor); + this.predicate = requireNonNull(predicate); + } + + @Override + public Subscriber apply(final Subscriber subscriber) { + return new OnErrorCompleteSubscriber(subscriber, predicate); + } + + private static final class OnErrorCompleteSubscriber implements Subscriber { + private final Subscriber subscriber; + private final Predicate predicate; + + private OnErrorCompleteSubscriber(final Subscriber subscriber, final Predicate predicate) { + this.subscriber = subscriber; + this.predicate = predicate; + } + + @Override + public void onSubscribe(final Cancellable cancellable) { + subscriber.onSubscribe(cancellable); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + + @Override + public void onError(final Throwable t) { + final boolean predicateResult; + try { + predicateResult = predicate.test(t); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + + if (predicateResult) { + subscriber.onComplete(); + } else { + subscriber.onError(t); + } + } + } +} diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompletePublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompletePublisher.java new file mode 100644 index 0000000000..b767a8c937 --- /dev/null +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorCompletePublisher.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import java.util.function.Predicate; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +final class OnErrorCompletePublisher extends AbstractSynchronousPublisherOperator { + private final Predicate predicate; + + OnErrorCompletePublisher(final Publisher original, Predicate predicate, + final Executor executor) { + super(original, executor); + this.predicate = requireNonNull(predicate); + } + + @Override + public Subscriber apply(final Subscriber subscriber) { + return new OnErrorCompleteSubscriber<>(subscriber, predicate); + } + + private static final class OnErrorCompleteSubscriber implements Subscriber { + private final Subscriber subscriber; + private final Predicate predicate; + + private OnErrorCompleteSubscriber(final Subscriber subscriber, + final Predicate predicate) { + this.subscriber = subscriber; + this.predicate = predicate; + } + + @Override + public void onSubscribe(final Subscription subscription) { + subscriber.onSubscribe(subscription); + } + + @Override + public void onNext(@Nullable final T t) { + subscriber.onNext(t); + } + + @Override + public void onError(final Throwable t) { + final boolean predicateResult; + try { + predicateResult = predicate.test(t); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + + if (predicateResult) { + subscriber.onComplete(); + } else { + subscriber.onError(t); + } + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + } +} diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapCompletable.java new file mode 100644 index 0000000000..86b97bf6be --- /dev/null +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapCompletable.java @@ -0,0 +1,82 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import io.servicetalk.concurrent.Cancellable; + +import java.util.function.Function; +import java.util.function.Predicate; + +import static java.util.Objects.requireNonNull; + +final class OnErrorMapCompletable extends AbstractSynchronousCompletableOperator { + private final Predicate predicate; + private final Function mapper; + + OnErrorMapCompletable(final Completable original, Predicate predicate, + Function mapper, final Executor executor) { + super(original, executor); + this.predicate = requireNonNull(predicate); + this.mapper = requireNonNull(mapper); + } + + @Override + public Subscriber apply(final Subscriber subscriber) { + return new ErrorMapSubscriber(subscriber); + } + + private final class ErrorMapSubscriber implements Subscriber { + private final Subscriber subscriber; + + private ErrorMapSubscriber(final Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void onSubscribe(final Cancellable cancellable) { + subscriber.onSubscribe(cancellable); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + + @Override + public void onError(final Throwable t) { + final boolean predicateResult; + try { + predicateResult = predicate.test(t); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + + if (predicateResult) { + final Throwable mappedCause; + try { + mappedCause = requireNonNull(mapper.apply(t)); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + subscriber.onError(mappedCause); + } else { + subscriber.onError(t); + } + } + } +} diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapPublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapPublisher.java new file mode 100644 index 0000000000..5b8126fc37 --- /dev/null +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapPublisher.java @@ -0,0 +1,86 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import java.util.function.Function; +import java.util.function.Predicate; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +final class OnErrorMapPublisher extends AbstractSynchronousPublisherOperator { + private final Predicate predicate; + private final Function mapper; + + OnErrorMapPublisher(Publisher original, Predicate predicate, + Function mapper, Executor executor) { + super(original, executor); + this.predicate = requireNonNull(predicate); + this.mapper = requireNonNull(mapper); + } + + @Override + public Subscriber apply(final Subscriber subscriber) { + return new ErrorMapSubscriber(subscriber); + } + + private final class ErrorMapSubscriber implements Subscriber { + private final Subscriber subscriber; + + private ErrorMapSubscriber(final Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void onSubscribe(final Subscription subscription) { + subscriber.onSubscribe(subscription); + } + + @Override + public void onNext(@Nullable final T t) { + subscriber.onNext(t); + } + + @Override + public void onError(final Throwable t) { + final boolean predicateResult; + try { + predicateResult = predicate.test(t); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + + if (predicateResult) { + final Throwable mappedCause; + try { + mappedCause = requireNonNull(mapper.apply(t)); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + subscriber.onError(mappedCause); + } else { + subscriber.onError(t); + } + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + } +} diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapSingle.java new file mode 100644 index 0000000000..f5da54c332 --- /dev/null +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorMapSingle.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * 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 io.servicetalk.concurrent.api; + +import io.servicetalk.concurrent.Cancellable; + +import java.util.function.Function; +import java.util.function.Predicate; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +final class OnErrorMapSingle extends AbstractSynchronousSingleOperator { + private final Predicate predicate; + private final Function mapper; + + OnErrorMapSingle(final Single original, Predicate predicate, + Function mapper, final Executor executor) { + super(original, executor); + this.predicate = requireNonNull(predicate); + this.mapper = requireNonNull(mapper); + } + + @Override + public Subscriber apply(final Subscriber subscriber) { + return new ErrorMapSubscriber(subscriber); + } + + private final class ErrorMapSubscriber implements Subscriber { + private final Subscriber subscriber; + + private ErrorMapSubscriber(final Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void onSubscribe(final Cancellable cancellable) { + subscriber.onSubscribe(cancellable); + } + + @Override + public void onSuccess(@Nullable final T result) { + subscriber.onSuccess(result); + } + + @Override + public void onError(final Throwable t) { + final boolean predicateResult; + try { + predicateResult = predicate.test(t); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + + if (predicateResult) { + final Throwable mappedCause; + try { + mappedCause = requireNonNull(mapper.apply(t)); + } catch (Throwable cause) { + subscriber.onError(cause); + return; + } + subscriber.onError(mappedCause); + } else { + subscriber.onError(t); + } + } + } +} diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java similarity index 63% rename from servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeCompletable.java rename to servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java index db524c44da..a897872184 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeCompletable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java @@ -20,46 +20,43 @@ import io.servicetalk.concurrent.internal.SignalOffloader; import java.util.function.Function; +import java.util.function.Predicate; import javax.annotation.Nullable; import static java.util.Objects.requireNonNull; -/** - * {@link Completable} as returned by {@link Completable#onErrorResume(Function)}. - */ -final class ResumeCompletable extends AbstractNoHandleSubscribeCompletable { +final class OnErrorResumeCompletable extends AbstractNoHandleSubscribeCompletable { private final Completable original; + private final Predicate predicate; private final Function nextFactory; - ResumeCompletable(Completable original, Function nextFactory, - Executor executor) { + OnErrorResumeCompletable(Completable original, Predicate predicate, + Function nextFactory, Executor executor) { super(executor); this.original = original; + this.predicate = requireNonNull(predicate); this.nextFactory = requireNonNull(nextFactory); } @Override void handleSubscribe(final Subscriber subscriber, final SignalOffloader signalOffloader, final AsyncContextMap contextMap, final AsyncContextProvider contextProvider) { - original.delegateSubscribe(new ResumeSubscriber(subscriber, nextFactory, signalOffloader, - contextMap, contextProvider), signalOffloader, contextMap, contextProvider); + original.delegateSubscribe(new ResumeSubscriber(subscriber, signalOffloader, contextMap, contextProvider), + signalOffloader, contextMap, contextProvider); } - private static final class ResumeSubscriber implements Subscriber { + private final class ResumeSubscriber implements Subscriber { private final Subscriber subscriber; private final SignalOffloader signalOffloader; private final AsyncContextMap contextMap; private final AsyncContextProvider contextProvider; @Nullable private SequentialCancellable sequentialCancellable; - @Nullable - private Function nextFactory; + private boolean resubscribed; - ResumeSubscriber(Subscriber subscriber, Function nextFactory, - SignalOffloader signalOffloader, AsyncContextMap contextMap, + ResumeSubscriber(Subscriber subscriber, SignalOffloader signalOffloader, AsyncContextMap contextMap, AsyncContextProvider contextProvider) { this.subscriber = subscriber; - this.nextFactory = nextFactory; this.signalOffloader = signalOffloader; this.contextMap = contextMap; this.contextProvider = contextProvider; @@ -71,8 +68,7 @@ public void onSubscribe(Cancellable cancellable) { sequentialCancellable = new SequentialCancellable(cancellable); subscriber.onSubscribe(sequentialCancellable); } else { - // Only a single re-subscribe is allowed. - nextFactory = null; + resubscribed = true; sequentialCancellable.nextCancellable(cancellable); } } @@ -84,26 +80,31 @@ public void onComplete() { @Override public void onError(Throwable throwable) { - if (nextFactory == null) { + if (resubscribed) { subscriber.onError(throwable); return; } final Completable next; try { - next = requireNonNull(nextFactory.apply(throwable)); + next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { t.addSuppressed(throwable); subscriber.onError(t); return; } - // We are subscribing to a new Completable which will send signals to the original Subscriber. This means - // that the threading semantics may differ with respect to the original Subscriber when we emit signals from - // the new Completable. This is the reason we use the original offloader now to offload signals which - // originate from this new Completable. - final Subscriber offloadedSubscriber = signalOffloader.offloadSubscriber( - contextProvider.wrapCompletableSubscriber(this, contextMap)); - next.subscribeInternal(offloadedSubscriber); + + if (next == null) { + subscriber.onError(throwable); + } else { + // We are subscribing to a new Completable which will send signals to the original Subscriber. This + // means that the threading semantics may differ with respect to the original Subscriber when we emit + // signals from the new Completable. This is the reason we use the original offloader now to offload + // signals which originate from this new Completable. + final Subscriber offloadedSubscriber = signalOffloader.offloadSubscriber( + contextProvider.wrapCompletableSubscriber(this, contextMap)); + next.subscribeInternal(offloadedSubscriber); + } } } } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumePublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java similarity index 62% rename from servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumePublisher.java rename to servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java index 046f79413f..cc09c60c21 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumePublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java @@ -18,51 +18,44 @@ import io.servicetalk.concurrent.internal.SignalOffloader; import java.util.function.Function; +import java.util.function.Predicate; import javax.annotation.Nullable; import static java.util.Objects.requireNonNull; -/** - * As returned by {@link Publisher#recoverWith(Function)}. - * - * @param Type of items emitted by this {@link Publisher}. - */ -final class ResumePublisher extends AbstractNoHandleSubscribePublisher { - +final class OnErrorResumePublisher extends AbstractNoHandleSubscribePublisher { private final Publisher original; + private final Predicate predicate; private final Function> nextFactory; - ResumePublisher(Publisher original, Function> nextFactory, - Executor executor) { + OnErrorResumePublisher(Publisher original, Predicate predicate, + Function> nextFactory, + Executor executor) { super(executor); this.original = original; + this.predicate = requireNonNull(predicate); this.nextFactory = requireNonNull(nextFactory); } @Override void handleSubscribe(final Subscriber subscriber, final SignalOffloader signalOffloader, final AsyncContextMap contextMap, final AsyncContextProvider contextProvider) { - original.delegateSubscribe( - new ResumeSubscriber<>(subscriber, nextFactory, signalOffloader, contextMap, contextProvider), + original.delegateSubscribe(new ResumeSubscriber(subscriber, signalOffloader, contextMap, contextProvider), signalOffloader, contextMap, contextProvider); } - private static final class ResumeSubscriber implements Subscriber { + private final class ResumeSubscriber implements Subscriber { private final Subscriber subscriber; private final SignalOffloader signalOffloader; private final AsyncContextMap contextMap; private final AsyncContextProvider contextProvider; @Nullable private SequentialSubscription sequentialSubscription; - @Nullable - private Function> nextFactory; + private boolean resubscribed; - ResumeSubscriber(Subscriber subscriber, - Function> nextFactory, - SignalOffloader signalOffloader, AsyncContextMap contextMap, + ResumeSubscriber(Subscriber subscriber, SignalOffloader signalOffloader, AsyncContextMap contextMap, AsyncContextProvider contextProvider) { this.subscriber = subscriber; - this.nextFactory = nextFactory; this.signalOffloader = signalOffloader; this.contextMap = contextMap; this.contextProvider = contextProvider; @@ -74,8 +67,7 @@ public void onSubscribe(Subscription s) { sequentialSubscription = new SequentialSubscription(s); subscriber.onSubscribe(sequentialSubscription); } else { - // Only a single re-subscribe is allowed. - nextFactory = null; + resubscribed = true; sequentialSubscription.switchTo(s); } } @@ -89,27 +81,31 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (nextFactory == null) { + if (resubscribed) { subscriber.onError(t); return; } final Publisher next; try { - next = requireNonNull(nextFactory.apply(t)); + next = predicate.test(t) ? requireNonNull(nextFactory.apply(t)) : null; } catch (Throwable throwable) { throwable.addSuppressed(t); subscriber.onError(throwable); return; } - // We are subscribing to a new Publisher which will send signals to the original Subscriber. This means - // that the threading semantics may differ with respect to the original Subscriber when we emit signals from - // the new Publisher. This is the reason we use the original offloader now to offload signals which - // originate from this new Publisher. - final Subscriber offloadedSubscriber = signalOffloader.offloadSubscriber( - contextProvider.wrapPublisherSubscriber(this, contextMap)); - next.subscribeInternal(offloadedSubscriber); + if (next == null) { + subscriber.onError(t); + } else { + // We are subscribing to a new Publisher which will send signals to the original Subscriber. This means + // that the threading semantics may differ with respect to the original Subscriber when we emit signals + // from the new Publisher. This is the reason we use the original offloader now to offload signals which + // originate from this new Publisher. + final Subscriber offloadedSubscriber = signalOffloader.offloadSubscriber( + contextProvider.wrapPublisherSubscriber(this, contextMap)); + next.subscribeInternal(offloadedSubscriber); + } } @Override diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java similarity index 59% rename from servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeSingle.java rename to servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java index c826246e29..1585f9b0b9 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/ResumeSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java @@ -20,54 +20,43 @@ import io.servicetalk.concurrent.internal.SignalOffloader; import java.util.function.Function; +import java.util.function.Predicate; import javax.annotation.Nullable; import static java.util.Objects.requireNonNull; -/** - * {@link Single} as returned by {@link Single#recoverWith(Function)}. - */ -final class ResumeSingle extends AbstractNoHandleSubscribeSingle { - +final class OnErrorResumeSingle extends AbstractNoHandleSubscribeSingle { private final Single original; + private final Predicate predicate; private final Function> nextFactory; - /** - * New instance. - * - * @param original Source. - * @param nextFactory For creating the next {@link Single}. - */ - ResumeSingle(Single original, Function> nextFactory, - Executor executor) { + OnErrorResumeSingle(Single original, Predicate predicate, + Function> nextFactory, Executor executor) { super(executor); this.original = original; + this.predicate = requireNonNull(predicate); this.nextFactory = requireNonNull(nextFactory); } @Override void handleSubscribe(final Subscriber subscriber, final SignalOffloader signalOffloader, final AsyncContextMap contextMap, final AsyncContextProvider contextProvider) { - original.delegateSubscribe(new ResumeSubscriber<>(subscriber, nextFactory, signalOffloader, - contextMap, contextProvider), signalOffloader, contextMap, contextProvider); + original.delegateSubscribe(new ResumeSubscriber(subscriber, signalOffloader, contextMap, contextProvider), + signalOffloader, contextMap, contextProvider); } - private static final class ResumeSubscriber implements Subscriber { + private final class ResumeSubscriber implements Subscriber { private final Subscriber subscriber; private final SignalOffloader signalOffloader; private final AsyncContextMap contextMap; private final AsyncContextProvider contextProvider; @Nullable private SequentialCancellable sequentialCancellable; - @Nullable - private Function> nextFactory; + private boolean resubscribed; - ResumeSubscriber(Subscriber subscriber, - Function> nextFactory, - SignalOffloader signalOffloader, AsyncContextMap contextMap, + ResumeSubscriber(Subscriber subscriber, SignalOffloader signalOffloader, AsyncContextMap contextMap, AsyncContextProvider contextProvider) { this.subscriber = subscriber; - this.nextFactory = nextFactory; this.signalOffloader = signalOffloader; this.contextMap = contextMap; this.contextProvider = contextProvider; @@ -79,8 +68,7 @@ public void onSubscribe(Cancellable cancellable) { sequentialCancellable = new SequentialCancellable(cancellable); subscriber.onSubscribe(sequentialCancellable); } else { - // Only a single re-subscribe is allowed. - nextFactory = null; + resubscribed = true; sequentialCancellable.nextCancellable(cancellable); } } @@ -92,26 +80,31 @@ public void onSuccess(@Nullable T result) { @Override public void onError(Throwable throwable) { - if (nextFactory == null) { + if (resubscribed) { subscriber.onError(throwable); return; } - Single next; + final Single next; try { - next = requireNonNull(nextFactory.apply(throwable)); + next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { t.addSuppressed(throwable); subscriber.onError(t); return; } - // We are subscribing to a new Single which will send signals to the original Subscriber. This means - // that the threading semantics may differ with respect to the original Subscriber when we emit signals from - // the new Single. This is the reason we use the original offloader now to offload signals which - // originate from this new Single. - final Subscriber offloadedSubscriber = signalOffloader.offloadSubscriber( - contextProvider.wrapSingleSubscriber(this, contextMap)); - next.subscribeInternal(offloadedSubscriber); + + if (next == null) { + subscriber.onError(throwable); + } else { + // We are subscribing to a new Single which will send signals to the original Subscriber. This means + // that the threading semantics may differ with respect to the original Subscriber when we emit signals + // from the new Single. This is the reason we use the original offloader now to offload signals which + // originate from this new Single. + final Subscriber offloadedSubscriber = signalOffloader.offloadSubscriber( + contextProvider.wrapSingleSubscriber(this, contextMap)); + next.subscribeInternal(offloadedSubscriber); + } } } } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java index 5d59f6e9f9..e316cecf13 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Publisher.java @@ -294,7 +294,7 @@ public final Publisher onErrorComplete(Class type) { * @see ReactiveX catch operator. */ public final Publisher onErrorComplete(Predicate predicate) { - return onErrorResume(predicate, t -> Publisher.empty()); + return new OnErrorCompletePublisher<>(this, predicate, executor); } /** @@ -458,8 +458,32 @@ public final Publisher onErrorMap( */ public final Publisher onErrorMap(Predicate predicate, Function mapper) { - requireNonNull(mapper); - return onErrorResume(predicate, t -> Publisher.failed(mapper.apply(t))); + return new OnErrorMapPublisher<>(this, predicate, mapper, executor); + } + + /** + * Recover from any error emitted by this {@link Publisher} by using another {@link Publisher} provided by the + * passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     List results;
+     *     try {
+     *         results = resultOfThisPublisher();
+     *     } catch (Throwable cause) {
+     *         // Note that nextFactory returning a error Publisher is like re-throwing (nextFactory shouldn't throw).
+     *         results = nextFactory.apply(cause);
+     *     }
+     *     return results;
+     * }
+ * + * @param nextFactory Returns the next {@link Publisher}, when this {@link Publisher} emits an error. + * @return A {@link Publisher} that recovers from an error from this {@link Publisher} by using another + * {@link Publisher} provided by the passed {@code nextFactory}. + * @see ReactiveX catch operator. + */ + public final Publisher onErrorResume(Function> nextFactory) { + return onErrorResume(t -> true, nextFactory); } /** @@ -526,34 +550,7 @@ public final Publisher onErrorResume( */ public final Publisher onErrorResume(Predicate predicate, Function> nextFactory) { - requireNonNull(predicate); - requireNonNull(nextFactory); - return onErrorResume(t -> predicate.test(t) ? nextFactory.apply(t) : Publisher.failed(t)); - } - - /** - * Recover from any error emitted by this {@link Publisher} by using another {@link Publisher} provided by the - * passed {@code nextFactory}. - *

- * This method provides similar capabilities to a try/catch block in sequential programming: - *

{@code
-     *     List results;
-     *     try {
-     *         results = resultOfThisPublisher();
-     *     } catch (Throwable cause) {
-     *         // Note that nextFactory returning a error Publisher is like re-throwing (nextFactory shouldn't throw).
-     *         results = nextFactory.apply(cause);
-     *     }
-     *     return results;
-     * }
- * - * @param nextFactory Returns the next {@link Publisher}, when this {@link Publisher} emits an error. - * @return A {@link Publisher} that recovers from an error from this {@link Publisher} by using another - * {@link Publisher} provided by the passed {@code nextFactory}. - * @see ReactiveX catch operator. - */ - public final Publisher onErrorResume(Function> nextFactory) { - return new ResumePublisher<>(this, nextFactory, executor); + return new OnErrorResumePublisher<>(this, predicate, nextFactory, executor); } /** diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java index 0b24f1cfad..f05f7642c2 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/Single.java @@ -284,8 +284,30 @@ public final Single onErrorMap( */ public final Single onErrorMap(Predicate predicate, Function mapper) { - requireNonNull(mapper); - return onErrorResume(predicate, t -> Single.failed(mapper.apply(t))); + return new OnErrorMapSingle<>(this, predicate, mapper, executor); + } + + /** + * Recover from any error emitted by this {@link Single} by using another {@link Single} provided by the + * passed {@code nextFactory}. + *

+ * This method provides similar capabilities to a try/catch block in sequential programming: + *

{@code
+     *     T result;
+     *     try {
+     *         result = resultOfThisSingle();
+     *     } catch (Throwable cause) {
+     *         // Note that nextFactory returning a error Single is like re-throwing (nextFactory shouldn't throw).
+     *         result = nextFactory.apply(cause);
+     *     }
+     *     return result;
+     * }
+ * @param nextFactory Returns the next {@link Single}, when this {@link Single} emits an error. + * @return A {@link Single} that recovers from an error from this {@link Single} by using another + * {@link Single} provided by the passed {@code nextFactory}. + */ + public final Single onErrorResume(Function> nextFactory) { + return onErrorResume(t -> true, nextFactory); } /** @@ -352,32 +374,7 @@ public final Single onErrorResume( */ public final Single onErrorResume(Predicate predicate, Function> nextFactory) { - requireNonNull(predicate); - requireNonNull(nextFactory); - return onErrorResume(t -> predicate.test(t) ? nextFactory.apply(t) : Single.failed(t)); - } - - /** - * Recover from any error emitted by this {@link Single} by using another {@link Single} provided by the - * passed {@code nextFactory}. - *

- * This method provides similar capabilities to a try/catch block in sequential programming: - *

{@code
-     *     T result;
-     *     try {
-     *         result = resultOfThisSingle();
-     *     } catch (Throwable cause) {
-     *         // Note that nextFactory returning a error Single is like re-throwing (nextFactory shouldn't throw).
-     *         result = nextFactory.apply(cause);
-     *     }
-     *     return result;
-     * }
- * @param nextFactory Returns the next {@link Single}, when this {@link Single} emits an error. - * @return A {@link Single} that recovers from an error from this {@link Single} by using another - * {@link Single} provided by the passed {@code nextFactory}. - */ - public final Single onErrorResume(Function> nextFactory) { - return new ResumeSingle<>(this, nextFactory, executor); + return new OnErrorResumeSingle<>(this, predicate, nextFactory, executor); } /** diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumePublisherTest.java similarity index 99% rename from servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java rename to servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumePublisherTest.java index e56f11af31..8af2fe74c6 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumePublisherTest.java @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class OnErrorResumePublisherTest { +public final class OnErrorOnErrorResumePublisherTest { private final TestPublisherSubscriber subscriber = new TestPublisherSubscriber<>(); private TestPublisher first = new TestPublisher<>(); diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumeSingleTest.java similarity index 98% rename from servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java rename to servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumeSingleTest.java index 8ea4781eda..1057388fdf 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumeSingleTest.java @@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class OnErrorResumeSingleTest { +public final class OnErrorOnErrorResumeSingleTest { private TestSingleSubscriber subscriber; private TestSingle first; diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumeCompletableTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeCompletableTest.java similarity index 98% rename from servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumeCompletableTest.java rename to servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeCompletableTest.java index 5896d337d1..de52d7b583 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/ResumeCompletableTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeCompletableTest.java @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class ResumeCompletableTest { +public final class OnErrorResumeCompletableTest { private TestCompletableSubscriber subscriber; private TestCompletable first; diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java deleted file mode 100644 index 58508e80a2..0000000000 --- a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/CompletableOnErrorCompleteTckTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright © 2021 Apple Inc. and the ServiceTalk project authors - * - * 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 io.servicetalk.concurrent.reactivestreams.tck; - -import io.servicetalk.concurrent.api.Completable; - -import org.testng.annotations.Test; - -@Test -public class CompletableOnErrorCompleteTckTest extends AbstractCompletableOperatorTckTest { - @Override - protected Completable composeCompletable(final Completable completable) { - return completable.onErrorComplete(); - } -} diff --git a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java b/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java deleted file mode 100644 index 6242b261f5..0000000000 --- a/servicetalk-concurrent-reactivestreams/src/test/java/io/servicetalk/concurrent/reactivestreams/tck/PublisherOnErrorCompleteTckTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright © 2021 Apple Inc. and the ServiceTalk project authors - * - * 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 io.servicetalk.concurrent.reactivestreams.tck; - -import io.servicetalk.concurrent.api.Publisher; - -import org.testng.annotations.Test; - -@Test -public class PublisherOnErrorCompleteTckTest extends AbstractPublisherOperatorTckTest { - @Override - protected Publisher composePublisher(final Publisher publisher, final int elements) { - return publisher.onErrorComplete(); - } -} From a3b68298712199618dcc439960619f8b81810f17 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Tue, 16 Mar 2021 10:15:13 -0700 Subject: [PATCH 3/8] remove suppressed for consistency --- .../io/servicetalk/concurrent/api/OnErrorResumeCompletable.java | 1 - .../io/servicetalk/concurrent/api/OnErrorResumePublisher.java | 1 - .../java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java | 1 - 3 files changed, 3 deletions(-) diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java index a897872184..a8c8b0b8e5 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java @@ -89,7 +89,6 @@ public void onError(Throwable throwable) { try { next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { - t.addSuppressed(throwable); subscriber.onError(t); return; } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java index cc09c60c21..b5f3c22055 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java @@ -90,7 +90,6 @@ public void onError(Throwable t) { try { next = predicate.test(t) ? requireNonNull(nextFactory.apply(t)) : null; } catch (Throwable throwable) { - throwable.addSuppressed(t); subscriber.onError(throwable); return; } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java index 1585f9b0b9..08bc809836 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java @@ -89,7 +89,6 @@ public void onError(Throwable throwable) { try { next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { - t.addSuppressed(throwable); subscriber.onError(t); return; } From 077e71be7d45837e1ddd4055b6e84d4b3bdc1150 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Tue, 16 Mar 2021 11:25:49 -0700 Subject: [PATCH 4/8] retain original cause in grpcRouter --- .../src/main/java/io/servicetalk/grpc/api/GrpcRouter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java index 8eabf4eab0..1a07f9545a 100644 --- a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java +++ b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcRouter.java @@ -403,11 +403,11 @@ public Publisher handle(final GrpcServiceContext ctx, final Publisher return request.firstOrError() .onErrorMap(t -> { if (t instanceof NoSuchElementException) { - return new GrpcStatus(INVALID_ARGUMENT, null, + return new GrpcStatus(INVALID_ARGUMENT, t, SINGLE_MESSAGE_EXPECTED_NONE_RECEIVED_MSG) .asException(); } else if (t instanceof IllegalArgumentException) { - return new GrpcStatus(INVALID_ARGUMENT, null, + return new GrpcStatus(INVALID_ARGUMENT, t, MORE_THAN_ONE_MESSAGE_RECEIVED_MSG).asException(); } else { return t; From 35ff9489d7809b018e5b916c4551163c76fb740d Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Tue, 16 Mar 2021 13:52:37 -0700 Subject: [PATCH 5/8] Revert "remove suppressed for consistency" --- .../io/servicetalk/concurrent/api/OnErrorResumeCompletable.java | 1 + .../io/servicetalk/concurrent/api/OnErrorResumePublisher.java | 1 + .../java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java | 1 + 3 files changed, 3 insertions(+) diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java index a8c8b0b8e5..a897872184 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java @@ -89,6 +89,7 @@ public void onError(Throwable throwable) { try { next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { + t.addSuppressed(throwable); subscriber.onError(t); return; } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java index b5f3c22055..cc09c60c21 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java @@ -90,6 +90,7 @@ public void onError(Throwable t) { try { next = predicate.test(t) ? requireNonNull(nextFactory.apply(t)) : null; } catch (Throwable throwable) { + throwable.addSuppressed(t); subscriber.onError(throwable); return; } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java index 08bc809836..1585f9b0b9 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java @@ -89,6 +89,7 @@ public void onError(Throwable throwable) { try { next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { + t.addSuppressed(throwable); subscriber.onError(t); return; } From 84e092ecfa678b3760041034a114a100be45b13e Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Tue, 16 Mar 2021 17:09:01 -0700 Subject: [PATCH 6/8] GrpcServerBuilder use onErrorReturn for less costly error recovery --- .../java/io/servicetalk/grpc/api/GrpcServerBuilder.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java index 803d6d1376..e37617687c 100644 --- a/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java +++ b/servicetalk-grpc-api/src/main/java/io/servicetalk/grpc/api/GrpcServerBuilder.java @@ -368,16 +368,15 @@ public Single handle(final HttpServiceContext ctx, try { handle = delegate().handle(ctx, request, responseFactory); } catch (Throwable cause) { - return convertToGrpcErrorResponse(ctx, responseFactory, cause); + return succeeded(convertToGrpcErrorResponse(ctx, responseFactory, cause)); } - return handle.onErrorResume(cause -> convertToGrpcErrorResponse(ctx, responseFactory, cause)); + return handle.onErrorReturn(cause -> convertToGrpcErrorResponse(ctx, responseFactory, cause)); } - private static Single convertToGrpcErrorResponse( + private static StreamingHttpResponse convertToGrpcErrorResponse( final HttpServiceContext ctx, final StreamingHttpResponseFactory responseFactory, final Throwable cause) { - return succeeded(newErrorResponse(responseFactory, null, cause, - ctx.executionContext().bufferAllocator())); + return newErrorResponse(responseFactory, null, cause, ctx.executionContext().bufferAllocator()); } } } From 0ddd2b879e1a5f61e7a47252a1de44abe1e4aa98 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Wed, 17 Mar 2021 12:08:12 -0700 Subject: [PATCH 7/8] update to junit5 --- .../concurrent/api/OnErrorCompletableTest.java | 13 ++++++++++--- .../concurrent/api/OnErrorPublisherTest.java | 13 ++++++++++--- ...herTest.java => OnErrorResumePublisherTest.java} | 2 +- ...SingleTest.java => OnErrorResumeSingleTest.java} | 2 +- .../concurrent/api/OnErrorSingleTest.java | 13 ++++++++++--- 5 files changed, 32 insertions(+), 11 deletions(-) rename servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/{OnErrorOnErrorResumePublisherTest.java => OnErrorResumePublisherTest.java} (99%) rename servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/{OnErrorOnErrorResumeSingleTest.java => OnErrorResumeSingleTest.java} (98%) diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java index b4001a3c5d..f45d1d70ef 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorCompletableTest.java @@ -18,7 +18,8 @@ import io.servicetalk.concurrent.internal.DeliberateException; import io.servicetalk.concurrent.test.internal.TestCompletableSubscriber; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static io.servicetalk.concurrent.api.Completable.completed; import static io.servicetalk.concurrent.api.Completable.failed; @@ -28,8 +29,14 @@ import static org.hamcrest.Matchers.is; public class OnErrorCompletableTest { - private final TestCompletableSubscriber subscriber = new TestCompletableSubscriber(); - private final TestCompletable first = new TestCompletable(); + private TestCompletableSubscriber subscriber; + private TestCompletable first; + + @BeforeEach + public void setUp() { + subscriber = new TestCompletableSubscriber(); + first = new TestCompletable(); + } @Test public void onErrorComplete() { diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java index d9970a013f..b95383e6f3 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorPublisherTest.java @@ -18,7 +18,8 @@ import io.servicetalk.concurrent.internal.DeliberateException; import io.servicetalk.concurrent.test.internal.TestPublisherSubscriber; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static io.servicetalk.concurrent.api.Publisher.empty; import static io.servicetalk.concurrent.api.Publisher.failed; @@ -28,8 +29,14 @@ import static org.hamcrest.Matchers.is; public class OnErrorPublisherTest { - private final TestPublisherSubscriber subscriber = new TestPublisherSubscriber<>(); - private final TestPublisher first = new TestPublisher<>(); + private TestPublisherSubscriber subscriber; + private TestPublisher first; + + @BeforeEach + public void setUp() { + subscriber = new TestPublisherSubscriber<>(); + first = new TestPublisher<>(); + } @Test public void onErrorComplete() { diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumePublisherTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java similarity index 99% rename from servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumePublisherTest.java rename to servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java index 8af2fe74c6..e56f11af31 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumePublisherTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumePublisherTest.java @@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class OnErrorOnErrorResumePublisherTest { +public final class OnErrorResumePublisherTest { private final TestPublisherSubscriber subscriber = new TestPublisherSubscriber<>(); private TestPublisher first = new TestPublisher<>(); diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumeSingleTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java similarity index 98% rename from servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumeSingleTest.java rename to servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java index 1057388fdf..8ea4781eda 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorOnErrorResumeSingleTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorResumeSingleTest.java @@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class OnErrorOnErrorResumeSingleTest { +public final class OnErrorResumeSingleTest { private TestSingleSubscriber subscriber; private TestSingle first; diff --git a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java index 5396cfa0ab..62a41f3458 100644 --- a/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java +++ b/servicetalk-concurrent-api/src/test/java/io/servicetalk/concurrent/api/OnErrorSingleTest.java @@ -18,7 +18,8 @@ import io.servicetalk.concurrent.internal.DeliberateException; import io.servicetalk.concurrent.test.internal.TestSingleSubscriber; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static io.servicetalk.concurrent.api.Single.failed; import static io.servicetalk.concurrent.api.Single.succeeded; @@ -28,8 +29,14 @@ import static org.hamcrest.Matchers.is; public class OnErrorSingleTest { - private final TestSingleSubscriber subscriber = new TestSingleSubscriber<>(); - private final TestSingle first = new TestSingle<>(); + private TestSingleSubscriber subscriber; + private TestSingle first; + + @BeforeEach + public void setUp() { + subscriber = new TestSingleSubscriber<>(); + first = new TestSingle<>(); + } @Test public void onErrorReturnMatch() { From 6f93f1e9eff45d2e1e0963806aab8558a69c4df9 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Thu, 18 Mar 2021 12:40:47 -0700 Subject: [PATCH 8/8] simplify onErrorResume control flow --- .../concurrent/api/OnErrorResumeCompletable.java | 7 +------ .../servicetalk/concurrent/api/OnErrorResumePublisher.java | 7 +------ .../io/servicetalk/concurrent/api/OnErrorResumeSingle.java | 7 +------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java index a897872184..f0164ea4cc 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeCompletable.java @@ -80,14 +80,9 @@ public void onComplete() { @Override public void onError(Throwable throwable) { - if (resubscribed) { - subscriber.onError(throwable); - return; - } - final Completable next; try { - next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; + next = !resubscribed && predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { t.addSuppressed(throwable); subscriber.onError(t); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java index cc09c60c21..c77894237b 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumePublisher.java @@ -81,14 +81,9 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (resubscribed) { - subscriber.onError(t); - return; - } - final Publisher next; try { - next = predicate.test(t) ? requireNonNull(nextFactory.apply(t)) : null; + next = !resubscribed && predicate.test(t) ? requireNonNull(nextFactory.apply(t)) : null; } catch (Throwable throwable) { throwable.addSuppressed(t); subscriber.onError(throwable); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java index 1585f9b0b9..bdca11decd 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/OnErrorResumeSingle.java @@ -80,14 +80,9 @@ public void onSuccess(@Nullable T result) { @Override public void onError(Throwable throwable) { - if (resubscribed) { - subscriber.onError(throwable); - return; - } - final Single next; try { - next = predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; + next = !resubscribed && predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { t.addSuppressed(throwable); subscriber.onError(t);