reactor.core.Exceptions Maven / Gradle / Ivy
/*
* 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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import reactor.util.annotation.Nullable;
/**
* Global Reactor Core Exception handling and utils to operate on.
*
* @author Stephane Maldini
* @see Reactive-Streams-Commons
*/
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("Operator has been terminated");
/**
* 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 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.
* @see #unwrapMultiple(Throwable)
*/
public static boolean addThrowable(AtomicReferenceFieldUpdater field,
T instance,
Throwable exception) {
for (; ; ) {
Throwable current = field.get(instance);
if (current == TERMINATED) {
return false;
}
if (current instanceof CompositeException) {
//this is ok, composite exceptions are never singletons
current.addSuppressed(exception);
return true;
}
Throwable update;
if (current == null) {
update = exception;
} else {
update = multiple(current, exception);
}
if (field.compareAndSet(instance, current, update)) {
return true;
}
}
}
/**
* Create a composite exception that wraps the given {@link Throwable Throwable(s)},
* as suppressed exceptions. Instances create by this method can be detected using the
* {@link #isMultiple(Throwable)} check. The {@link #unwrapMultiple(Throwable)} method
* will correctly unwrap these to a {@link List} of the suppressed exceptions. Note
* that is will also be consistent in producing a List for other types of exceptions
* by putting the input inside a single-element List.
*
* @param throwables the exceptions to wrap into a composite
* @return a composite exception with a standard message, and the given throwables as
* suppressed exceptions
* @see #addThrowable(AtomicReferenceFieldUpdater, Object, Throwable)
*/
public static RuntimeException multiple(Throwable... throwables) {
CompositeException multiple = new CompositeException();
//noinspection ConstantConditions
if (throwables != null) {
for (Throwable t : throwables) {
//this is ok, multiple is always a new non-singleton instance
multiple.addSuppressed(t);
}
}
return multiple;
}
/**
* Create a composite exception that wraps the given {@link Throwable Throwable(s)},
* as suppressed exceptions. Instances create by this method can be detected using the
* {@link #isMultiple(Throwable)} check. The {@link #unwrapMultiple(Throwable)} method
* will correctly unwrap these to a {@link List} of the suppressed exceptions. Note
* that is will also be consistent in producing a List for other types of exceptions
* by putting the input inside a single-element List.
*
* @param throwables the exceptions to wrap into a composite
* @return a composite exception with a standard message, and the given throwables as
* suppressed exceptions
* @see #addThrowable(AtomicReferenceFieldUpdater, Object, Throwable)
*/
public static RuntimeException multiple(Iterable throwables) {
RuntimeException multiple = new RuntimeException("Multiple exceptions");
//noinspection ConstantConditions
if (throwables != null) {
for (Throwable t : throwables) {
//this is ok, multiple is always a new non-singleton instance
multiple.addSuppressed(t);
}
}
return multiple;
}
/**
* Prepare an unchecked {@link RuntimeException} that will bubble upstream if thrown
* by an operator. 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);
}
/**
* Return a singleton {@link RejectedExecutionException}
*
* @return a singleton {@link RejectedExecutionException}
*/
public static RejectedExecutionException failWithRejected() {
return REJECTED_EXECUTION;
}
/**
* Return a singleton {@link RejectedExecutionException} with a message indicating
* the reason is due to the scheduler not being time-capable
*
* @return a singleton {@link RejectedExecutionException}
*/
public static RejectedExecutionException failWithRejectedNotTimeCapable() {
return NOT_TIME_CAPABLE_REJECTED_EXECUTION;
}
/**
* Return a new {@link RejectedExecutionException} with standard message and cause,
* unless the {@code cause} is already a {@link RejectedExecutionException} created
* via {@link #failWithRejected(Throwable)} (not the singleton-producing variants).
*
* @param cause the original exception that caused the rejection
* @return a new {@link RejectedExecutionException} with standard message and cause
*/
public static RejectedExecutionException failWithRejected(Throwable cause) {
if (cause instanceof ReactorRejectedExecutionException) {
return (RejectedExecutionException) cause;
}
return new ReactorRejectedExecutionException("Scheduler unavailable", cause);
}
/**
* 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(@Nullable 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(@Nullable 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(@Nullable 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(@Nullable Throwable t) {
return t != null && t.getClass().equals(ErrorCallbackNotImplemented.class);
}
/**
* Check a {@link Throwable} to see if it is a composite, as created by {@link #multiple(Throwable...)}.
*
* @param t the {@link Throwable} to check, {@literal null} always yields {@literal false}
* @return true if the Throwable is an instance created by {@link #multiple(Throwable...)}, false otherwise
*/
public static boolean isMultiple(@Nullable Throwable t) {
return t instanceof CompositeException;
}
/**
* @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)}.
*
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 the instance type
*
* @return the previously masked throwable
*/
@Nullable
public static Throwable terminate(AtomicReferenceFieldUpdater 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:
* - {@code BubblingException} (as detectable by {@link #isBubbling(Throwable)})
* - {@code ErrorCallbackNotImplemented} (as detectable by {@link #isErrorCallbackNotImplemented(Throwable)})
* - {@link VirtualMachineError}
- {@link ThreadDeath}
- {@link LinkageError}
*
* @param t the exception to evaluate
*/
public static void throwIfFatal(@Nullable 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:
* - {@link VirtualMachineError}
- {@link ThreadDeath}
* - {@link LinkageError}
*
* @param t the exception to evaluate
*/
public static void throwIfJvmFatal(@Nullable 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 or current one if null
*/
public static Throwable unwrap(Throwable t) {
Throwable _t = t;
while (_t instanceof ReactiveException) {
_t = _t.getCause();
}
return _t == null ? t : _t;
}
/**
* Attempt to unwrap a {@link Throwable} into a {@link List} of Throwables. This is
* only done on the condition that said Throwable is a composite exception built by
* {@link #multiple(Throwable...)}, in which case the list contains the exceptions
* wrapped as suppressed exceptions in the composite. In any other case, the list
* only contains the input Throwable (or is empty in case of null input).
*
* @param potentialMultiple the {@link Throwable} to unwrap if multiple
* @return a {@link List} of the exceptions suppressed by the {@link Throwable} if
* multiple, or a List containing the Throwable otherwise. Null input results in an
* empty List.
*/
public static List unwrapMultiple(@Nullable Throwable potentialMultiple) {
if (potentialMultiple == null) {
return Collections.emptyList();
}
if (isMultiple(potentialMultiple)) {
return Arrays.asList(potentialMultiple.getSuppressed());
}
return Collections.singletonList(potentialMultiple);
}
/**
* Safely suppress a {@link Throwable} on a {@link RuntimeException}. The returned
* {@link RuntimeException}, bearing the suppressed exception, is most often the same
* as the original exception unless:
*
* - original and tentatively suppressed exceptions are one and the same: do
* nothing but return the original.
* - original is one of the singleton {@link RejectedExecutionException} created
* by Reactor: make a copy the {@link RejectedExecutionException}, add the
* suppressed exception to it and return that copy.
*
*
* @param original the original {@link RuntimeException} to bear a suppressed exception
* @param suppressed the {@link Throwable} to suppress
* @return the (most of the time original) {@link RuntimeException} bearing the
* suppressed {@link Throwable}
*/
public static final RuntimeException addSuppressed(RuntimeException original, Throwable suppressed) {
if (original == suppressed) {
return original;
}
if (original == REJECTED_EXECUTION || original == NOT_TIME_CAPABLE_REJECTED_EXECUTION) {
RejectedExecutionException ree = new RejectedExecutionException(original.getMessage());
ree.addSuppressed(suppressed);
return ree;
}
else {
original.addSuppressed(suppressed);
return original;
}
}
/**
* Safely suppress a {@link Throwable} on an original {@link Throwable}. The returned
* {@link Throwable}, bearing the suppressed exception, is most often the same
* as the original one unless:
*
* - original and tentatively suppressed exceptions are one and the same: do
* nothing but return the original.
* - original is one of the singleton {@link RejectedExecutionException} created
* by Reactor: make a copy the {@link RejectedExecutionException}, add the
* suppressed exception to it and return that copy.
* - original is a special internal TERMINATED singleton instance: return it
* without suppressing the exception.
*
*
* @param original the original {@link Throwable} to bear a suppressed exception
* @param suppressed the {@link Throwable} to suppress
* @return the (most of the time original) {@link Throwable} bearing the
* suppressed {@link Throwable}
*/
public static final Throwable addSuppressed(Throwable original, final Throwable suppressed) {
if (original == suppressed) {
return original;
}
if (original == TERMINATED) {
return original;
}
if (original == REJECTED_EXECUTION || original == NOT_TIME_CAPABLE_REJECTED_EXECUTION) {
RejectedExecutionException ree = new RejectedExecutionException(original.getMessage());
ree.addSuppressed(suppressed);
return ree;
}
else {
original.addSuppressed(suppressed);
return original;
}
}
Exceptions() {
}
static final RejectedExecutionException REJECTED_EXECUTION = new RejectedExecutionException("Scheduler unavailable");
static final RejectedExecutionException NOT_TIME_CAPABLE_REJECTED_EXECUTION =
new RejectedExecutionException(
"Scheduler is not capable of time-based scheduling");
static class CompositeException extends ReactiveException {
CompositeException() {
super("Multiple exceptions");
}
private static final long serialVersionUID = 8070744939537687606L;
}
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);
}
}
static final class ReactorRejectedExecutionException extends RejectedExecutionException {
ReactorRejectedExecutionException(String message, Throwable cause) {
super(message, cause);
}
}
}