/* * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package reactor.core; import java.util.Objects; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** * Global Reactor Core Exception handling and utils to operate on. * * @author Stephane Maldini * @see <a href="https://github.com/reactor/reactive-streams-commons">Reactive-Streams-Commons</a> */ public abstract class Exceptions { /** * A common error message used when a reactive streams source doesn't seem to respect * backpressure signals, resulting in an operator's internal queue to be full. */ public static final String BACKPRESSURE_ERROR_QUEUE_FULL = "Queue is full: Reactive Streams source doesn't respect backpressure"; /** * A singleton instance of a Throwable indicating a terminal state for exceptions, * don't leak this! */ @SuppressWarnings("ThrowableInstanceNeverThrown") public static final Throwable TERMINATED = new Throwable("No further exceptions"); /** * Update an empty atomic reference with the given exception, or combine further added * exceptions together as suppressed exceptions under a root Throwable with * the {@code "Multiple exceptions"} message, if the atomic reference already holds * one. This is short-circuited if the reference contains {@link #TERMINATED}. * * @param <T> the parent instance type * @param field the target field updater * @param instance the parent instance for the field * @param exception the Throwable to add. * * @return true if added, false if the field contained the {@link #TERMINATED} * instance. */ public static <T> boolean addThrowable(AtomicReferenceFieldUpdater<T, Throwable> field, T instance, Throwable exception) { for (; ; ) { Throwable current = field.get(instance); if (current == TERMINATED) { return false; } Throwable update; if (current == null) { update = exception; } else { update = multiple(current, exception); } if (field.compareAndSet(instance, current, update)) { return true; } } } public static RuntimeException multiple(Throwable... throwables) { RuntimeException multiple = new RuntimeException("Multiple exceptions"); if (throwables != null) { for (Throwable t : throwables) { multiple.addSuppressed(t); } } return multiple; } /** * @return a new {@link NullPointerException} with a cause message abiding to reactive * stream specification rule 2.13. */ public static NullPointerException argumentIsNullException() { return new NullPointerException("Spec 2.13: Signal/argument cannot be null"); } /** * Prepare an unchecked {@link RuntimeException} that will bubble upstream if thrown * by an operator. <p>This method invokes {@link #throwIfFatal(Throwable)}. * * @param t the root cause * * @return an unchecked exception that should choose bubbling up over error callback * path */ public static RuntimeException bubble(Throwable t) { throwIfFatal(t); return new BubblingException(t); } /** * @return a new {@link IllegalStateException} with a cause message abiding to * reactive stream specification rule 2.12. */ public static IllegalStateException duplicateOnSubscribeException() { return new IllegalStateException( "Spec. Rule 2.12 - Subscriber.onSubscribe MUST NOT be called more than once (based on object equality)"); } /** * Return an {@link UnsupportedOperationException} indicating that the error callback * on a subscriber was not implemented, yet an error was propagated. * * @param cause original error not processed by a receiver. * @return an {@link UnsupportedOperationException} indicating the error callback was * not implemented and holding the original propagated error. * @see #isErrorCallbackNotImplemented(Throwable) */ public static UnsupportedOperationException errorCallbackNotImplemented(Throwable cause) { Objects.requireNonNull(cause, "cause"); return new ErrorCallbackNotImplemented(cause); } /** * An exception that is propagated upward and considered as "fatal" as per Reactive * Stream limited list of exceptions allowed to bubble. It is not meant to be common * error resolution but might assist implementors in dealing with boundaries (queues, * combinations and async). * * @return a {@link RuntimeException} that can be identified via {@link #isCancel} * @see #isCancel(Throwable) */ public static RuntimeException failWithCancel() { return new CancelException(); } /** * Return an {@link IllegalStateException} indicating the receiver is overrun by * more signals than expected in case of a bounded queue, or more generally that data * couldn't be emitted due to a lack of request * * @return an {@link IllegalStateException} * @see #isOverflow(Throwable) */ public static IllegalStateException failWithOverflow() { return new OverflowException("The receiver is overrun by more signals than expected (bounded queue...)"); } /** * Return an {@link IllegalStateException} indicating the receiver is overrun by * more signals than expected in case of a bounded queue or more generally that data * couldn't be emitted due to a lack of request * * @param message the exception's message * @return an {@link IllegalStateException} * @see #isOverflow(Throwable) */ public static IllegalStateException failWithOverflow(String message) { return new OverflowException(message); } /** * Check if the given exception represents an {@link #failWithOverflow() overflow}. * @param t the {@link Throwable} error to check * @return true if the given {@link Throwable} represents an overflow. */ public static boolean isOverflow(Throwable t) { return t instanceof OverflowException; } /** * Check if the given exception is a {@link #bubble(Throwable) bubbled} wrapped exception. * @param t the {@link Throwable} error to check * @return true if given {@link Throwable} is a bubbled wrapped exception. */ public static boolean isBubbling(Throwable t) { return t instanceof BubblingException; } /** * Check if the given error is a {@link #failWithCancel() cancel signal}. * @param t the {@link Throwable} error to check * @return true if given {@link Throwable} is a cancellation token. */ public static boolean isCancel(Throwable t) { return t instanceof CancelException; } /** * Check if the given error is a {@link #errorCallbackNotImplemented(Throwable) callback not implemented} * exception, in which case its {@link Throwable#getCause() cause} will be the propagated * error that couldn't be processed. * * @param t the {@link Throwable} error to check * @return true if given {@link Throwable} is a callback not implemented exception. */ public static boolean isErrorCallbackNotImplemented(Throwable t) { return t != null && t.getClass().equals(ErrorCallbackNotImplemented .class); } /** * @param elements the invalid requested demand * * @return a new {@link IllegalArgumentException} with a cause message abiding to * reactive stream specification rule 3.9. */ public static IllegalArgumentException nullOrNegativeRequestException(long elements) { return new IllegalArgumentException( "Spec. Rule 3.9 - Cannot request a non strictly positive number: " + elements); } /** * Prepare an unchecked {@link RuntimeException} that should be propagated * downstream through {@link org.reactivestreams.Subscriber#onError(Throwable)}. * <p>This method invokes {@link #throwIfFatal(Throwable)}. * * @param t the root cause * * @return an unchecked exception to propagate through onError signals. */ public static RuntimeException propagate(Throwable t) { throwIfFatal(t); if (t instanceof RuntimeException) { return (RuntimeException) t; } return new ReactiveException(t); } /** * Atomic utility to safely mark a volatile throwable reference with a terminal * marker. * * @param field the atomic container * @param instance the reference instance * @param <T> the instance type * * @return the previously masked throwable */ public static <T> Throwable terminate(AtomicReferenceFieldUpdater<T, Throwable> field, T instance) { Throwable current = field.get(instance); if (current != TERMINATED) { current = field.getAndSet(instance, TERMINATED); } return current; } /** * Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error * varieties. These varieties are as follows: <ul> * <li>{@code BubblingException} (as detectable by {@link #isBubbling(Throwable)})</li> * <li>{@code ErrorCallbackNotImplemented} (as detectable by {@link #isErrorCallbackNotImplemented(Throwable)})</li> * <li>{@link VirtualMachineError}</li> <li>{@link ThreadDeath}</li> <li>{@link LinkageError}</li> </ul> * * @param t the exception to evaluate */ public static void throwIfFatal(Throwable t) { if (t instanceof BubblingException) { throw (BubblingException) t; } if (t instanceof ErrorCallbackNotImplemented) { throw (ErrorCallbackNotImplemented) t; } throwIfJvmFatal(t); } /** * Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error * varieties native to the JVM. These varieties are as follows: * <ul> <li>{@link VirtualMachineError}</li> <li>{@link ThreadDeath}</li> * <li>{@link LinkageError}</li> </ul> * * @param t the exception to evaluate */ public static void throwIfJvmFatal(Throwable t) { if (t instanceof VirtualMachineError) { throw (VirtualMachineError) t; } if (t instanceof ThreadDeath) { throw (ThreadDeath) t; } if (t instanceof LinkageError) { throw (LinkageError) t; } } /** * Unwrap a particular {@code Throwable} only if it is was wrapped via * {@link #bubble(Throwable) bubble} or {@link #propagate(Throwable) propagate}. * * @param t the exception to unwrap * * @return the unwrapped exception */ public static Throwable unwrap(Throwable t) { Throwable _t = t; while (_t instanceof ReactiveException) { _t = _t.getCause(); } return _t; } Exceptions() { } static class BubblingException extends ReactiveException { BubblingException(String message) { super(message); } BubblingException(Throwable cause) { super(cause); } private static final long serialVersionUID = 2491425277432776142L; } /** * An exception that is propagated downward through {@link org.reactivestreams.Subscriber#onError(Throwable)} */ static class ReactiveException extends RuntimeException { ReactiveException(Throwable cause) { super(cause); } ReactiveException(String message) { super(message); } @Override public synchronized Throwable fillInStackTrace() { return getCause() != null ? getCause().fillInStackTrace() : super.fillInStackTrace(); } private static final long serialVersionUID = 2491425227432776143L; } static final class ErrorCallbackNotImplemented extends UnsupportedOperationException { ErrorCallbackNotImplemented(Throwable cause) { super(cause); } @Override public synchronized Throwable fillInStackTrace() { return this; } private static final long serialVersionUID = 2491425227432776143L; } /** * An error signal from downstream subscribers consuming data when their state is * denying any additional event. * * @author Stephane Maldini */ static final class CancelException extends BubblingException { CancelException() { super("The subscriber has denied dispatching"); } private static final long serialVersionUID = 2491425227432776144L; } static final class OverflowException extends IllegalStateException { OverflowException(String s) { super(s); } } }