From 3d260462dce23d126892ecf5206a3c15fecad3c5 Mon Sep 17 00:00:00 2001 From: Idel Pivnitskiy Date: Wed, 11 May 2022 17:10:56 -0500 Subject: [PATCH] Avoid self-suppression for exceptions (#2211) Motivation: In case a processing function re-throws the original exception, we risk to face `IllegalArgumentException("Self-suppression not permitted)` when operator adds a re-thrown exception as suppressed. Modifications: - Add utility that verifies the suppressed exception is not the same as the original one; Result: No `IllegalArgumentException("Self-suppression not permitted)`. --- .../concurrent/api/AbstractPubToSingle.java | 3 ++- .../api/BeforeFinallyCompletable.java | 4 ++-- .../concurrent/api/BeforeFinallyPublisher.java | 4 ++-- .../concurrent/api/BeforeFinallySingle.java | 4 ++-- .../api/BeforeSubscriberCompletable.java | 4 ++-- .../api/BeforeSubscriberPublisher.java | 4 ++-- .../concurrent/api/BeforeSubscriberSingle.java | 4 ++-- .../api/CompletableMergeSubscriber.java | 5 ++--- .../concurrent/api/CompositeCancellable.java | 3 ++- .../api/CompositeExceptionUtils.java | 4 +++- .../api/OnErrorResumeCompletable.java | 4 ++-- .../concurrent/api/OnErrorResumePublisher.java | 12 ++++++------ .../concurrent/api/OnErrorResumeSingle.java | 4 ++-- .../concurrent/api/RedoPublisher.java | 3 ++- .../concurrent/api/RedoWhenPublisher.java | 3 ++- .../concurrent/api/RetrySingle.java | 5 +++-- .../concurrent/api/RetryWhenSingle.java | 4 ++-- .../concurrent/api/TestCompletable.java | 3 ++- .../concurrent/api/TestPublisher.java | 3 ++- .../servicetalk/concurrent/api/TestSingle.java | 3 ++- .../internal/TerminalNotification.java | 7 +++---- .../dns/discovery/netty/DefaultDnsClient.java | 3 ++- .../grpc/netty/ProtocolCompatibilityTest.java | 3 ++- .../api/HttpDataSourceTransformations.java | 3 ++- .../AbstractLifecycleObserverHttpFilter.java | 3 ++- .../netty/H2ClientParentConnectionContext.java | 3 ++- .../http/utils/BeforeFinallyHttpOperator.java | 7 ++++--- servicetalk-serialization-api/build.gradle | 1 + .../serialization/api/DefaultSerializer.java | 5 +++-- servicetalk-test-resources/build.gradle | 5 +++-- .../servicetalk/test/resources/TestUtils.java | 3 ++- servicetalk-transport-api/build.gradle | 1 + .../api/CatchAllTransportObserver.java | 3 ++- .../build.gradle | 1 + .../transport/netty/internal/Flush.java | 3 ++- .../netty/internal/WriteStreamSubscriber.java | 3 ++- .../netty/internal/EmbeddedDuplexChannel.java | 3 ++- .../utils/internal/ThrowableUtils.java | 18 +++++++++++++++--- 38 files changed, 97 insertions(+), 61 deletions(-) diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractPubToSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractPubToSingle.java index bf25095474..11239c2430 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractPubToSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/AbstractPubToSingle.java @@ -27,6 +27,7 @@ import static io.servicetalk.concurrent.Cancellable.IGNORE_CANCEL; import static io.servicetalk.concurrent.api.SubscriberApiUtils.unwrapNullUnchecked; import static io.servicetalk.concurrent.internal.SubscriberUtils.checkDuplicateSubscription; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; abstract class AbstractPubToSingle extends AbstractNoHandleSubscribeSingle { private final Publisher source; @@ -110,7 +111,7 @@ void terminate(Object terminal) { subscriber.onSubscribe(IGNORE_CANCEL); } catch (Throwable t) { if (terminal instanceof Throwable) { - ((Throwable) terminal).addSuppressed(t); + addSuppressed((Throwable) terminal, t); } else { LOGGER.warn("Unexpected exception from onSubscribe from subscriber {}. Discarding result {}.", subscriber, terminal, t); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyCompletable.java index f08ab5ce44..4570438cfa 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyCompletable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyCompletable.java @@ -19,6 +19,7 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class BeforeFinallyCompletable extends AbstractSynchronousCompletableOperator { @@ -78,8 +79,7 @@ public void onError(Throwable cause) { doFinally.onError(cause); } } catch (Throwable error) { - error.addSuppressed(cause); - original.onError(error); + original.onError(addSuppressed(error, cause)); return; } original.onError(cause); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyPublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyPublisher.java index afd403ee24..648b6b758e 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyPublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallyPublisher.java @@ -17,6 +17,7 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class BeforeFinallyPublisher extends AbstractSynchronousPublisherOperator { @@ -93,8 +94,7 @@ public void onError(Throwable cause) { doFinally.onError(cause); } } catch (Throwable err) { - err.addSuppressed(cause); - original.onError(err); + original.onError(addSuppressed(err, cause)); return; } original.onError(cause); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallySingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallySingle.java index ca9fa546d6..26392ad9df 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallySingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeFinallySingle.java @@ -19,6 +19,7 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class BeforeFinallySingle extends AbstractSynchronousSingleOperator { @@ -79,8 +80,7 @@ public void onError(Throwable cause) { doFinally.onError(cause); } } catch (Throwable err) { - err.addSuppressed(cause); - original.onError(err); + original.onError(addSuppressed(err, cause)); return; } original.onError(cause); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberCompletable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberCompletable.java index b12b6682ab..6ac0ec71ab 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberCompletable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberCompletable.java @@ -19,6 +19,7 @@ import java.util.function.Supplier; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class BeforeSubscriberCompletable extends AbstractSynchronousCompletableOperator { @@ -67,8 +68,7 @@ public void onError(Throwable t) { try { subscriber.onError(t); } catch (Throwable cause) { - t.addSuppressed(cause); - original.onError(t); + original.onError(addSuppressed(t, cause)); return; } original.onError(t); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberPublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberPublisher.java index c9a00d532c..f3b220bc6c 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberPublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberPublisher.java @@ -17,6 +17,7 @@ import java.util.function.Supplier; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class BeforeSubscriberPublisher extends AbstractSynchronousPublisherOperator { @@ -70,8 +71,7 @@ public void onError(Throwable t) { try { subscriber.onError(t); } catch (Throwable cause) { - t.addSuppressed(cause); - original.onError(t); + original.onError(addSuppressed(t, cause)); return; } original.onError(t); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberSingle.java index b6cf245018..7b30fc5173 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/BeforeSubscriberSingle.java @@ -19,6 +19,7 @@ import java.util.function.Supplier; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class BeforeSubscriberSingle extends AbstractSynchronousSingleOperator { @@ -66,8 +67,7 @@ public void onError(Throwable t) { try { subscriber.onError(t); } catch (Throwable cause) { - t.addSuppressed(cause); - original.onError(t); + original.onError(addSuppressed(t, cause)); return; } original.onError(t); diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompletableMergeSubscriber.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompletableMergeSubscriber.java index 5647ece984..e2ff3aff83 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompletableMergeSubscriber.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompletableMergeSubscriber.java @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import javax.annotation.Nullable; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; abstract class CompletableMergeSubscriber implements Subscriber { @@ -68,9 +69,7 @@ public final void onError(Throwable t) { return; } } else { - Throwable tmpT = (Throwable) terminalNotification; - tmpT.addSuppressed(t); - t = tmpT; + t = addSuppressed((Throwable) terminalNotification, t); break; } } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeCancellable.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeCancellable.java index 9f6389e7a0..9aa16dae87 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeCancellable.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeCancellable.java @@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import static io.servicetalk.utils.internal.PlatformDependent.throwException; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; /** @@ -56,7 +57,7 @@ public void cancel() { if (t == null) { t = tt; } else { - t.addSuppressed(tt); + addSuppressed(t, tt); } } } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeExceptionUtils.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeExceptionUtils.java index bc7db72045..7175eb98ac 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeExceptionUtils.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/CompositeExceptionUtils.java @@ -17,6 +17,8 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; + final class CompositeExceptionUtils { /** * Default to {@code 1} so {@link Throwable#addSuppressed(Throwable)} will not be used by default. @@ -33,7 +35,7 @@ static void addPendingError(AtomicIntegerFieldUpdater updater, T owner, i if (newSize < 0) { updater.set(owner, Integer.MAX_VALUE); } else if (newSize < maxDelayedErrors && original != causeToAdd) { - original.addSuppressed(causeToAdd); + addSuppressed(original, causeToAdd); } else { updater.decrementAndGet(owner); } 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 3291807cb2..20f1968538 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 @@ -23,6 +23,7 @@ import java.util.function.Predicate; import javax.annotation.Nullable; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class OnErrorResumeCompletable extends AbstractNoHandleSubscribeCompletable { @@ -86,8 +87,7 @@ public void onError(Throwable throwable) { requireNonNull(parent.nextFactory.apply(throwable)) : null; } catch (Throwable t) { - t.addSuppressed(throwable); - subscriber.onError(t); + subscriber.onError(addSuppressed(t, throwable)); 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 34ddc970e9..23642bae6e 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 @@ -21,6 +21,7 @@ import java.util.function.Predicate; import javax.annotation.Nullable; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class OnErrorResumePublisher extends AbstractNoHandleSubscribePublisher { @@ -76,18 +77,17 @@ public void onNext(T t) { } @Override - public void onError(Throwable t) { + public void onError(Throwable throwable) { final Publisher next; try { - next = !resubscribed && predicate.test(t) ? requireNonNull(nextFactory.apply(t)) : null; - } catch (Throwable throwable) { - throwable.addSuppressed(t); - subscriber.onError(throwable); + next = !resubscribed && predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; + } catch (Throwable t) { + subscriber.onError(addSuppressed(t, throwable)); return; } if (next == null) { - subscriber.onError(t); + subscriber.onError(throwable); } else { final Subscriber offloadedSubscriber = contextProvider.wrapPublisherSubscriber(this, contextMap); 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 59db57a067..8c92ac938e 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 @@ -23,6 +23,7 @@ import java.util.function.Predicate; import javax.annotation.Nullable; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class OnErrorResumeSingle extends AbstractNoHandleSubscribeSingle { @@ -81,8 +82,7 @@ public void onError(Throwable throwable) { try { next = !resubscribed && predicate.test(throwable) ? requireNonNull(nextFactory.apply(throwable)) : null; } catch (Throwable t) { - t.addSuppressed(throwable); - subscriber.onError(t); + subscriber.onError(addSuppressed(t, throwable)); return; } diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoPublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoPublisher.java index 15c1d03aef..321f2d529f 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoPublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoPublisher.java @@ -22,6 +22,7 @@ import java.util.function.IntPredicate; import static io.servicetalk.concurrent.internal.TerminalNotification.complete; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; /** * {@link Publisher} to do {@link Publisher#repeat(IntPredicate)} and {@link Publisher#retry(BiIntPredicate)} @@ -116,7 +117,7 @@ private void tryRedo(TerminalNotification notification) { } catch (Throwable cause) { Throwable originalCause = notification.cause(); if (originalCause != null) { - cause.addSuppressed(originalCause); + addSuppressed(cause, originalCause); } subscriber.onError(cause); return; diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoWhenPublisher.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoWhenPublisher.java index 8b528d37c8..b61ff6e9da 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoWhenPublisher.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RedoWhenPublisher.java @@ -25,6 +25,7 @@ import java.util.function.IntFunction; import static io.servicetalk.concurrent.internal.TerminalNotification.complete; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; /** @@ -143,7 +144,7 @@ private void redoIfRequired(TerminalNotification terminalNotification) { } catch (Throwable cause) { Throwable originalCause = terminalNotification.cause(); if (originalCause != null) { - cause.addSuppressed(originalCause); + addSuppressed(cause, originalCause); } subscriber.onError(cause); return; diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetrySingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetrySingle.java index b39ec8cb4a..6d55652d3e 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetrySingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetrySingle.java @@ -21,6 +21,8 @@ import javax.annotation.Nullable; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; + /** * A {@link Single} implementation as returned by {@link Single#retry(BiIntPredicate)}. * @@ -97,8 +99,7 @@ public void onError(Throwable t) { try { shouldRetry = retrySingle.shouldRetry.test(++retryCount, t); } catch (Throwable cause) { - cause.addSuppressed(t); - target.onError(cause); + target.onError(addSuppressed(cause, t)); return; } if (shouldRetry) { diff --git a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetryWhenSingle.java b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetryWhenSingle.java index 7b997a0dd7..805d615793 100644 --- a/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetryWhenSingle.java +++ b/servicetalk-concurrent-api/src/main/java/io/servicetalk/concurrent/api/RetryWhenSingle.java @@ -22,6 +22,7 @@ import javax.annotation.Nullable; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; /** @@ -104,8 +105,7 @@ public void onError(Throwable t) { try { retryDecider = requireNonNull(retrySingle.shouldRetry.apply(++retryCount, t)); } catch (Throwable cause) { - cause.addSuppressed(t); - target.onError(cause); + target.onError(addSuppressed(cause, t)); return; } diff --git a/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestCompletable.java b/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestCompletable.java index badce3f177..71537878ac 100644 --- a/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestCompletable.java +++ b/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestCompletable.java @@ -31,6 +31,7 @@ import javax.annotation.Nullable; import static io.servicetalk.utils.internal.PlatformDependent.throwException; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; /** @@ -150,7 +151,7 @@ private Subscriber checkSubscriberAndExceptions() { final RuntimeException exception = new RuntimeException("Unexpected exception(s) encountered", exceptions.get(0)); for (int i = 1; i < exceptions.size(); i++) { - exception.addSuppressed(exceptions.get(i)); + addSuppressed(exception, exceptions.get(i)); } throw exception; } diff --git a/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestPublisher.java b/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestPublisher.java index 916ceb847c..e43dc65656 100644 --- a/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestPublisher.java +++ b/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestPublisher.java @@ -30,6 +30,7 @@ import javax.annotation.Nullable; import static io.servicetalk.utils.internal.PlatformDependent.throwException; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; /** @@ -176,7 +177,7 @@ private Subscriber checkSubscriberAndExceptions() { final RuntimeException exception = new RuntimeException("Unexpected exception(s) encountered", exceptions.get(0)); for (int i = 1; i < exceptions.size(); i++) { - exception.addSuppressed(exceptions.get(i)); + addSuppressed(exception, exceptions.get(i)); } throw exception; } diff --git a/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestSingle.java b/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestSingle.java index d16613b645..4d61626f6e 100644 --- a/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestSingle.java +++ b/servicetalk-concurrent-api/src/testFixtures/java/io/servicetalk/concurrent/api/TestSingle.java @@ -31,6 +31,7 @@ import static io.servicetalk.concurrent.test.internal.AwaitUtils.await; import static io.servicetalk.utils.internal.PlatformDependent.throwException; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; /** @@ -153,7 +154,7 @@ private Subscriber checkSubscriberAndExceptions() { final RuntimeException exception = new RuntimeException("Unexpected exception(s) encountered", exceptions.get(0)); for (int i = 1; i < exceptions.size(); i++) { - exception.addSuppressed(exceptions.get(i)); + addSuppressed(exception, exceptions.get(i)); } throw exception; } diff --git a/servicetalk-concurrent-internal/src/main/java/io/servicetalk/concurrent/internal/TerminalNotification.java b/servicetalk-concurrent-internal/src/main/java/io/servicetalk/concurrent/internal/TerminalNotification.java index fdd2a35de3..9d65f7c559 100644 --- a/servicetalk-concurrent-internal/src/main/java/io/servicetalk/concurrent/internal/TerminalNotification.java +++ b/servicetalk-concurrent-internal/src/main/java/io/servicetalk/concurrent/internal/TerminalNotification.java @@ -21,6 +21,7 @@ import java.util.Objects; import javax.annotation.Nullable; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; /** @@ -80,8 +81,7 @@ public void terminate(PublisherSource.Subscriber subscriber, Throwable additi subscriber.onError(additionalCause); } else { assert cause != null; - cause.addSuppressed(additionalCause); - subscriber.onError(cause); + subscriber.onError(addSuppressed(cause, additionalCause)); } } @@ -102,8 +102,7 @@ public void terminate(Subscriber subscriber, Throwable additionalCause) { subscriber.onError(additionalCause); } else { assert cause != null; - cause.addSuppressed(additionalCause); - subscriber.onError(cause); + subscriber.onError(addSuppressed(cause, additionalCause)); } } 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 8e3e4ef7f2..c91f269af7 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 @@ -90,6 +90,7 @@ import static io.servicetalk.transport.netty.internal.BuilderUtils.datagramChannel; import static io.servicetalk.transport.netty.internal.BuilderUtils.socketChannel; import static io.servicetalk.transport.netty.internal.EventLoopAwareNettyIoExecutors.toEventLoopAwareNettyIoExecutor; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.nio.ByteBuffer.wrap; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -698,7 +699,7 @@ private void reportResolutionFailed(@Nullable final DnsResolutionObserver resolu try { resolutionObserver.resolutionFailed(cause); } catch (Throwable unexpected) { - unexpected.addSuppressed(cause); + addSuppressed(unexpected, cause); LOGGER.warn("Unexpected exception from {} while reporting DNS resolution failure", resolutionObserver, unexpected); } diff --git a/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/ProtocolCompatibilityTest.java b/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/ProtocolCompatibilityTest.java index 897be0c627..66cdfe088f 100644 --- a/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/ProtocolCompatibilityTest.java +++ b/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/ProtocolCompatibilityTest.java @@ -126,6 +126,7 @@ import static io.servicetalk.test.resources.DefaultTestCerts.serverPemHostname; import static io.servicetalk.transport.api.SslProvider.OPENSSL; import static io.servicetalk.transport.netty.internal.AddressUtils.localAddress; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.time.Duration.ofMillis; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -980,7 +981,7 @@ private static void closeAll(final AutoCloseable... acs) { if (re == null) { re = new RuntimeException("Failure(s) when closing: " + Arrays.toString(acs)); } - re.addSuppressed(t); + addSuppressed(re, t); } } diff --git a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HttpDataSourceTransformations.java b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HttpDataSourceTransformations.java index 95e7d9984f..84a86dae82 100644 --- a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HttpDataSourceTransformations.java +++ b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/HttpDataSourceTransformations.java @@ -40,6 +40,7 @@ import static io.servicetalk.concurrent.api.Single.succeeded; import static io.servicetalk.concurrent.api.SourceAdapters.toSource; import static io.servicetalk.concurrent.internal.SubscriberUtils.checkDuplicateSubscription; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.lang.Integer.MAX_VALUE; import static java.lang.System.nanoTime; import static java.util.Objects.requireNonNull; @@ -240,7 +241,7 @@ private boolean validateNext(@Nullable Object next) { try { iterator.close(); } catch (Throwable cause) { - e.addSuppressed(cause); + addSuppressed(e, cause); } throw e; } diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/AbstractLifecycleObserverHttpFilter.java b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/AbstractLifecycleObserverHttpFilter.java index 40e6183fb5..2deb50510d 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/AbstractLifecycleObserverHttpFilter.java +++ b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/AbstractLifecycleObserverHttpFilter.java @@ -49,6 +49,7 @@ import static io.servicetalk.concurrent.api.Single.defer; import static io.servicetalk.context.api.ContextMap.Key.newKey; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; abstract class AbstractLifecycleObserverHttpFilter implements HttpExecutionStrategyInfluencer { @@ -288,7 +289,7 @@ private static void safeReport(final Consumer onError, final Throwabl try { onError.accept(t); } catch (Throwable unexpected) { - unexpected.addSuppressed(t); + addSuppressed(unexpected, t); LOGGER.warn("Unexpected exception from {} while reporting a '{}' event", observer, eventName, unexpected); } } diff --git a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/H2ClientParentConnectionContext.java b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/H2ClientParentConnectionContext.java index cd3b7fd7b3..0f83332bc1 100644 --- a/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/H2ClientParentConnectionContext.java +++ b/servicetalk-http-netty/src/main/java/io/servicetalk/http/netty/H2ClientParentConnectionContext.java @@ -89,6 +89,7 @@ import static io.servicetalk.transport.netty.internal.ChannelCloseUtils.close; import static io.servicetalk.transport.netty.internal.ChannelSet.CHANNEL_CLOSEABLE_KEY; import static io.servicetalk.transport.netty.internal.CloseHandler.forNonPipelined; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; final class H2ClientParentConnectionContext extends H2ParentConnectionContext { @@ -375,7 +376,7 @@ private void childChannelActive(Future future, try { close(streamChannel, cause); } catch (Throwable unexpected) { - unexpected.addSuppressed(cause); + addSuppressed(unexpected, cause); LOGGER.warn("Unexpected exception while handling the original cause", unexpected); } } else { diff --git a/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/BeforeFinallyHttpOperator.java b/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/BeforeFinallyHttpOperator.java index 4f3cea2583..7ef0a1d959 100644 --- a/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/BeforeFinallyHttpOperator.java +++ b/servicetalk-http-utils/src/main/java/io/servicetalk/http/utils/BeforeFinallyHttpOperator.java @@ -32,6 +32,7 @@ import javax.annotation.Nullable; import static io.servicetalk.concurrent.api.SourceAdapters.toSource; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.util.Objects.requireNonNull; import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater; @@ -281,7 +282,7 @@ public void onError(final Throwable t) { beforeFinally.onError(t); } } catch (Throwable cause) { - t.addSuppressed(cause); + addSuppressed(t, cause); } subscriber.onError(t); return; @@ -298,7 +299,7 @@ public void onError(final Throwable t) { try { beforeFinally.onError(t); } catch (Throwable cause) { - t.addSuppressed(cause); + addSuppressed(t, cause); } try { subscriber.onError(t); @@ -398,7 +399,7 @@ public void onError(final Throwable t) { return; } } catch (Throwable cause) { - t.addSuppressed(cause); + addSuppressed(t, cause); } subscriber.onError(t); } diff --git a/servicetalk-serialization-api/build.gradle b/servicetalk-serialization-api/build.gradle index 6e563cd0b9..e6f9526997 100644 --- a/servicetalk-serialization-api/build.gradle +++ b/servicetalk-serialization-api/build.gradle @@ -26,6 +26,7 @@ dependencies { implementation project(":servicetalk-annotations") implementation project(":servicetalk-concurrent-api-internal") implementation project(":servicetalk-concurrent-internal") + implementation project(":servicetalk-utils-internal") implementation "com.google.code.findbugs:jsr305:$jsr305Version" testImplementation testFixtures(project(":servicetalk-concurrent-api")) diff --git a/servicetalk-serialization-api/src/main/java/io/servicetalk/serialization/api/DefaultSerializer.java b/servicetalk-serialization-api/src/main/java/io/servicetalk/serialization/api/DefaultSerializer.java index f19a1955e2..f2d5c1f56b 100644 --- a/servicetalk-serialization-api/src/main/java/io/servicetalk/serialization/api/DefaultSerializer.java +++ b/servicetalk-serialization-api/src/main/java/io/servicetalk/serialization/api/DefaultSerializer.java @@ -35,6 +35,7 @@ import javax.annotation.Nullable; import static io.servicetalk.concurrent.api.SourceAdapters.toSource; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.lang.Integer.MAX_VALUE; import static java.lang.Math.max; import static java.lang.Math.min; @@ -326,7 +327,7 @@ private static CloseableIterable deserializeAndClose(final S source, try { deSerializer.close(); } catch (SerializationException e) { - throwable.addSuppressed(e); + addSuppressed(throwable, e); } throw throwable; } @@ -352,7 +353,7 @@ private static void closeIterator(CloseableIterator iterator, @Nullable Seria iterator.close(); // May throw in case of incomplete accumulated data } catch (Exception e) { if (cause != null) { - cause.addSuppressed(e); + addSuppressed(cause, e); throw cause; } if (e instanceof SerializationException) { diff --git a/servicetalk-test-resources/build.gradle b/servicetalk-test-resources/build.gradle index 06a26b6486..e58bbb6319 100644 --- a/servicetalk-test-resources/build.gradle +++ b/servicetalk-test-resources/build.gradle @@ -19,11 +19,12 @@ apply plugin: "io.servicetalk.servicetalk-gradle-plugin-internal-library" dependencies { testImplementation enforcedPlatform("org.junit:junit-bom:$junit5Version") - implementation project(":servicetalk-annotations") - api "org.apache.logging.log4j:log4j-core:$log4jVersion" api "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" api "org.hamcrest:hamcrest:$hamcrestVersion" + implementation project(":servicetalk-annotations") + implementation project(":servicetalk-utils-internal") + testImplementation "org.junit.jupiter:junit-jupiter-api" } diff --git a/servicetalk-test-resources/src/main/java/io/servicetalk/test/resources/TestUtils.java b/servicetalk-test-resources/src/main/java/io/servicetalk/test/resources/TestUtils.java index 0c1158d1bf..c153bded15 100644 --- a/servicetalk-test-resources/src/main/java/io/servicetalk/test/resources/TestUtils.java +++ b/servicetalk-test-resources/src/main/java/io/servicetalk/test/resources/TestUtils.java @@ -21,6 +21,7 @@ import java.util.Queue; +import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; import static java.lang.Integer.min; /** @@ -57,7 +58,7 @@ public static void assertNoAsyncErrors(final String message, final Queue