All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.spi.impl.AbstractInvocationFuture Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, 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 com.hazelcast.spi.impl;

import com.hazelcast.core.ExecutionCallback;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.instance.impl.OutOfMemoryErrorDispatcher;
import com.hazelcast.internal.util.ConcurrencyUtil;
import com.hazelcast.internal.util.executor.UnblockableThread;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.impl.operationservice.WrappableException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

import static com.hazelcast.internal.util.ConcurrencyUtil.CALLER_RUNS;
import static com.hazelcast.internal.util.ExceptionUtil.cloneExceptionWithFixedAsyncStackTrace;
import static com.hazelcast.internal.util.ExceptionUtil.sneakyThrow;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
import static java.util.concurrent.locks.LockSupport.park;
import static java.util.concurrent.locks.LockSupport.parkNanos;
import static java.util.concurrent.locks.LockSupport.unpark;

/**
 * Custom implementation of {@link java.util.concurrent.CompletableFuture}.
 *
 * @param 
 */
@SuppressFBWarnings(value = "DLS_DEAD_STORE_OF_CLASS_LITERAL", justification = "Recommended way to prevent classloading bug")
@SuppressWarnings({"checkstyle:methodcount", "checkstyle:ClassDataAbstractionCoupling", "checkstyle:ClassFanOutComplexity"})
public abstract class AbstractInvocationFuture extends InternalCompletableFuture {

    static final Object UNRESOLVED = new Object() {
        @Override
        public String toString() {
            return "UNRESOLVED";
        }
    };

    private static final AtomicReferenceFieldUpdater STATE_UPDATER =
            newUpdater(AbstractInvocationFuture.class, Object.class, "state");

    // reduce the risk of rare disastrous classloading in first call to
    // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
    static {
        @SuppressWarnings("unused")
        Class ensureLoaded = LockSupport.class;
    }

    protected final ILogger logger;

    /**
     * This field contains the state of the future. If the future is not
     * complete, the state can be:
     * 
    *
  1. {@link #UNRESOLVED}: no response is available.
  2. *
  3. Thread instance: no response is available and a thread has * blocked on completion (e.g. future.get)
  4. *
  5. {@link WaitNode} or {@link Waiter} instance: in case of multiple * callback registrations or future.gets.
  6. *
* If the state is anything else, it is completed. *

* The reason why a single future.get or registered ExecutionCallback * doesn't create a WaitNode is that we don't want to cause additional * litter since most of our API calls are a get or a single ExecutionCallback. *

* The state field is replaced using a cas, so registration or setting a * response is an atomic operation and therefore not prone to data-races. * There is no need to use synchronized blocks. */ protected volatile Object state = UNRESOLVED; protected AbstractInvocationFuture(@Nonnull ILogger logger) { this.logger = logger; } // methods to be overridden protected abstract String invocationToString(); // invokes resolve(value), then handles outcome with get() exception throwing conventions protected abstract V resolveAndThrowIfException(Object state) throws ExecutionException, InterruptedException; protected abstract TimeoutException newTimeoutException(long timeout, TimeUnit unit); // CompletionStage API implementation @Override public InternalCompletableFuture thenApply(@Nonnull Function fn) { return thenApplyAsync(fn, defaultExecutor()); } @Override public InternalCompletableFuture thenApplyAsync(@Nonnull Function fn) { return thenApplyAsync(fn, defaultExecutor()); } @Override public InternalCompletableFuture thenApplyAsync(@Nonnull Function fn, Executor executor) { requireNonNull(fn); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockApply(fn, executor, future); } else { Object result = registerWaiter(new ApplyNode(future, fn), executor); if (result != UNRESOLVED) { unblockApply(fn, executor, future); } } return future; } @Override public InternalCompletableFuture thenAccept(@Nonnull Consumer action) { return thenAcceptAsync(action, defaultExecutor()); } @Override public InternalCompletableFuture thenAcceptAsync(@Nonnull Consumer action) { return thenAcceptAsync(action, defaultExecutor()); } @Override public InternalCompletableFuture thenAcceptAsync(@Nonnull Consumer action, @Nonnull Executor executor) { requireNonNull(action); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockAccept(action, executor, future); } else { Object result = registerWaiter(new AcceptNode<>(future, action), executor); if (result != UNRESOLVED) { unblockAccept(action, executor, future); } } return future; } @Override public InternalCompletableFuture thenRun(@Nonnull Runnable action) { return thenRunAsync(action, defaultExecutor()); } @Override public InternalCompletableFuture thenRunAsync(@Nonnull Runnable action) { return thenRunAsync(action, defaultExecutor()); } @Override public InternalCompletableFuture thenRunAsync(@Nonnull Runnable action, @Nonnull Executor executor) { requireNonNull(action); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockRun(action, executor, future); } else { Object result = registerWaiter(new RunNode(future, action), executor); if (result != UNRESOLVED) { unblockRun(action, executor, future); } } return future; } @Override public InternalCompletableFuture handle(@Nonnull BiFunction fn) { return handleAsync(fn, defaultExecutor()); } @Override public InternalCompletableFuture handleAsync(@Nonnull BiFunction fn) { return handleAsync(fn, defaultExecutor()); } @Override public InternalCompletableFuture handleAsync(@Nonnull BiFunction fn, @Nonnull Executor executor) { requireNonNull(fn); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockHandle(fn, executor, future); } else { Object result = registerWaiter(new HandleNode(future, fn), executor); if (result != UNRESOLVED) { unblockHandle(fn, executor, future); } } return future; } @Override public InternalCompletableFuture whenComplete(@Nonnull BiConsumer action) { return whenCompleteAsync(action, defaultExecutor()); } @Override public InternalCompletableFuture whenCompleteAsync(@Nonnull BiConsumer action) { return whenCompleteAsync(action, defaultExecutor()); } @Override public InternalCompletableFuture whenCompleteAsync(@Nonnull BiConsumer action, @Nonnull Executor executor) { requireNonNull(action); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockWhenComplete(action, executor, future); } else { Object result = registerWaiter(new WhenCompleteNode(future, action), executor); if (result != UNRESOLVED) { unblockWhenComplete(action, executor, future); } } return future; } @Override public InternalCompletableFuture thenCompose(@Nonnull Function> fn) { return thenComposeAsync(fn, defaultExecutor()); } @Override public InternalCompletableFuture thenComposeAsync(@Nonnull Function> fn) { return thenComposeAsync(fn, defaultExecutor()); } @Override public InternalCompletableFuture thenComposeAsync(@Nonnull Function> fn, @Nonnull Executor executor) { requireNonNull(fn); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockCompose(fn, executor, future); } else { Object result = registerWaiter(new ComposeNode(future, fn), executor); if (result != UNRESOLVED) { unblockCompose(fn, executor, future); } } return future; } @Override public InternalCompletableFuture thenCombine(@Nonnull CompletionStage other, @Nonnull BiFunction fn) { return thenCombineAsync(other, fn, defaultExecutor()); } @Override public InternalCompletableFuture thenCombineAsync(@Nonnull CompletionStage other, @Nonnull BiFunction fn) { return thenCombineAsync(other, fn, defaultExecutor()); } @Override public InternalCompletableFuture thenCombineAsync(@Nonnull CompletionStage other, @Nonnull BiFunction fn, @Nonnull Executor executor) { requireNonNull(other); requireNonNull(fn); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockCombine(other, fn, executor, future); } else { Object result = registerWaiter(new CombineNode(future, other.toCompletableFuture(), fn), executor); if (result != UNRESOLVED) { unblockCombine(other, fn, executor, future); } } return future; } @Override public InternalCompletableFuture thenAcceptBoth(@Nonnull CompletionStage other, @Nonnull BiConsumer action) { return thenAcceptBothAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture thenAcceptBothAsync(@Nonnull CompletionStage other, @Nonnull BiConsumer action) { return thenAcceptBothAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture thenAcceptBothAsync(@Nonnull CompletionStage other, @Nonnull BiConsumer action, @Nonnull Executor executor) { requireNonNull(action); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); final CompletableFuture otherFuture = (other instanceof CompletableFuture) ? (CompletableFuture) other : other.toCompletableFuture(); if (isDone()) { unblockAcceptBoth(otherFuture, action, executor, future); } else { Object result = registerWaiter(new AcceptBothNode<>(future, otherFuture, action), executor); if (result != UNRESOLVED) { unblockAcceptBoth(otherFuture, action, executor, future); } } return future; } @Override public InternalCompletableFuture runAfterBoth(@Nonnull CompletionStage other, @Nonnull Runnable action) { return runAfterBothAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture runAfterBothAsync(@Nonnull CompletionStage other, @Nonnull Runnable action) { return runAfterBothAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture runAfterBothAsync(@Nonnull CompletionStage other, @Nonnull Runnable action, @Nonnull Executor executor) { requireNonNull(other); requireNonNull(action); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); final CompletableFuture otherFuture = (other instanceof CompletableFuture) ? (CompletableFuture) other : other.toCompletableFuture(); if (isDone()) { unblockRunAfterBoth(otherFuture, action, executor, future); } else { Object result = registerWaiter(new RunAfterBothNode<>(future, otherFuture, action), executor); if (result != UNRESOLVED) { unblockRunAfterBoth(otherFuture, action, executor, future); } } return future; } @Override public InternalCompletableFuture applyToEither(@Nonnull CompletionStage other, @Nonnull Function fn) { return applyToEitherAsync(other, fn, defaultExecutor()); } @Override public InternalCompletableFuture applyToEitherAsync(@Nonnull CompletionStage other, @Nonnull Function fn) { return applyToEitherAsync(other, fn, defaultExecutor()); } @Override public InternalCompletableFuture applyToEitherAsync(@Nonnull CompletionStage other, @Nonnull Function fn, @Nonnull Executor executor) { requireNonNull(other); requireNonNull(fn); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); final CompletableFuture otherFuture = (other instanceof CompletableFuture) ? (CompletableFuture) other : other.toCompletableFuture(); if (isDone()) { unblockApplyToEither(fn, executor, future); } else { ApplyEither waiter = new ApplyEither<>(future, fn); Object result = registerWaiter(waiter, executor); if (result == UNRESOLVED) { otherFuture.whenCompleteAsync(waiter, executor); return future; } else { unblockApplyToEither(fn, executor, future); } } return future; } @Override public InternalCompletableFuture acceptEither(@Nonnull CompletionStage other, @Nonnull Consumer action) { return acceptEitherAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture acceptEitherAsync(@Nonnull CompletionStage other, @Nonnull Consumer action) { return acceptEitherAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture acceptEitherAsync(@Nonnull CompletionStage other, @Nonnull Consumer action, @Nonnull Executor executor) { requireNonNull(other); requireNonNull(action); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); final CompletableFuture otherFuture = (other instanceof CompletableFuture) ? (CompletableFuture) other : other.toCompletableFuture(); if (isDone()) { unblockAcceptEither(action, executor, future); } else { AcceptEither waiter = new AcceptEither<>(future, action); Object result = registerWaiter(waiter, executor); if (result == UNRESOLVED) { otherFuture.whenCompleteAsync(waiter, executor); return future; } else { unblockAcceptEither(action, executor, future); } } return future; } @Override public InternalCompletableFuture runAfterEither(CompletionStage other, Runnable action) { return runAfterEitherAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture runAfterEitherAsync(@Nonnull CompletionStage other, @Nonnull Runnable action) { return runAfterEitherAsync(other, action, defaultExecutor()); } @Override public InternalCompletableFuture runAfterEitherAsync(@Nonnull CompletionStage other, @Nonnull Runnable action, @Nonnull Executor executor) { requireNonNull(other); requireNonNull(action); requireNonNull(executor); final InternalCompletableFuture future = newCompletableFuture(); final CompletableFuture otherFuture = (other instanceof CompletableFuture) ? (CompletableFuture) other : other.toCompletableFuture(); if (isDone()) { unblockRunAfterEither(action, executor, future); } else { RunAfterEither waiter = new RunAfterEither(future, action); Object result = registerWaiter(waiter, executor); if (result == UNRESOLVED) { otherFuture.whenCompleteAsync(waiter, executor); return future; } else { unblockRunAfterEither(action, executor, future); } } return future; } @Override public InternalCompletableFuture exceptionally(@Nonnull Function fn) { requireNonNull(fn); final InternalCompletableFuture future = newCompletableFuture(); if (isDone()) { unblockExceptionally(fn, future); } else { Object result = registerWaiter(new ExceptionallyNode<>(future, fn), null); if (result != UNRESOLVED) { unblockExceptionally(fn, future); } } return future; } @Override public InternalCompletableFuture toCompletableFuture() { return this; } boolean compareAndSetState(Object oldState, Object newState) { return STATE_UPDATER.compareAndSet(this, oldState, newState); } protected final Object getState() { return state; } @Override public final boolean isDone() { return isDone(state); } protected void onInterruptDetected() { } @Override public boolean cancel(boolean mayInterruptIfRunning) { return completeExceptionally(new CancellationException()); } @Override public boolean isCancelled() { return isStateCancelled(state); } @Override public boolean isCompletedExceptionally() { return (state instanceof ExceptionalResult); } @Override public final V join() { final Object response = registerWaiter(Thread.currentThread(), null); if (response != UNRESOLVED) { return resolveAndThrowWithJoinConvention(response); } boolean interrupted = false; try { do { manageParking(0); if (isDone()) { return resolveAndThrowWithJoinConvention(state); } else if (Thread.interrupted()) { interrupted = true; onInterruptDetected(); } } while (true); } finally { restoreInterrupt(interrupted); } } /** * Similarly to {@link #join()}, returns the value when complete or throws an unchecked exception if * completed exceptionally. Unlike {@link #join()}, checked exceptions are not wrapped in {@link CompletionException}; * rather they are wrapped in {@link com.hazelcast.core.HazelcastException}s. * * @return the result */ @Override public V joinInternal() { final Object response = registerWaiter(Thread.currentThread(), null); if (response != UNRESOLVED) { // no registration was done since a value is available. return resolveAndThrowForJoinInternal(response); } boolean interrupted = false; try { do { manageParking(0); if (isDone()) { return resolveAndThrowForJoinInternal(state); } else if (Thread.interrupted()) { interrupted = true; onInterruptDetected(); } } while (true); } finally { restoreInterrupt(interrupted); } } V resolveAndThrowForJoinInternal(Object unresolved) { Object resolved = resolve(unresolved); if (!(resolved instanceof ExceptionalResult)) { return (V) resolved; } else { throw sneakyThrow(((ExceptionalResult) resolved).wrapForJoinInternal()); } } @Override public final V get() throws InterruptedException, ExecutionException { Object response = registerWaiter(Thread.currentThread(), null); if (response != UNRESOLVED) { // no registration was done since a value is available. return resolveAndThrowIfException(response); } boolean interrupted = false; try { do { manageParking(0); if (isDone()) { return resolveAndThrowIfException(state); } else if (Thread.interrupted()) { interrupted = true; onInterruptDetected(); } } while (true); } finally { restoreInterrupt(interrupted); } } @Override public final V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { Object response = registerWaiter(Thread.currentThread(), null); if (response != UNRESOLVED) { return resolveAndThrowIfException(response); } long deadlineNanos = System.nanoTime() + unit.toNanos(timeout); boolean interrupted = false; try { long timeoutNanos = unit.toNanos(timeout); while (timeoutNanos > 0) { manageParking(timeoutNanos); timeoutNanos = deadlineNanos - System.nanoTime(); if (isDone()) { return resolveAndThrowIfException(state); } else if (Thread.interrupted()) { interrupted = true; onInterruptDetected(); } } } finally { restoreInterrupt(interrupted); } unregisterWaiter(Thread.currentThread()); throw newTimeoutException(timeout, unit); } // Use when the caller thread is a ForkJoinWorkerThread class ManagedBlocker implements ForkJoinPool.ManagedBlocker { private final long timeoutNanos; ManagedBlocker(long timeoutNanos) { this.timeoutNanos = timeoutNanos; } @Override public boolean isReleasable() { return Thread.currentThread().isInterrupted() || isDone(); } @Override public boolean block() throws InterruptedException { if (isReleasable()) { return true; } else if (timeoutNanos == 0) { park(); } else if (timeoutNanos > 0) { parkNanos(timeoutNanos); } return isReleasable(); } } private void manageParking(long timeoutNanos) { try { // if the caller thread is a ForkJoinWorkerThread if (ForkJoinTask.inForkJoinPool()) { ForkJoinPool.managedBlock(new ManagedBlocker(timeoutNanos)); } else if (timeoutNanos == 0) { park(); } else if (timeoutNanos > 0) { parkNanos(timeoutNanos); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @Override public V getNow(V valueIfAbsent) { return isDone() ? join() : valueIfAbsent; } @Override public boolean completeExceptionally(Throwable ex) { return completeExceptionallyInternal(ex); } @Override public void obtrudeValue(V value) { obtrude0(value); } @Override public void obtrudeException(Throwable ex) { obtrude0(wrapThrowable(ex)); } private void obtrude0(Object value) { for (; ; ) { final Object oldState = state; if (compareAndSetState(oldState, value)) { onComplete(); unblockAll(oldState, defaultExecutor()); break; } } } @Override public int getNumberOfDependents() { int dependents = 0; Object index = state; while (index instanceof WaitNode) { dependents++; index = ((WaitNode) index).next; } if (index instanceof Waiter) { // the first dependent registered with a default executor // is not wrapped in a WaitNode dependents++; } return dependents; } private void unblockAll(Object waiter, Executor executor) { while (waiter != null) { if (waiter instanceof Thread) { unpark((Thread) waiter); return; } else if (waiter.getClass() == WaitNode.class) { WaitNode waitNode = (WaitNode) waiter; unblockAll(waitNode.waiter, waitNode.executor); waiter = waitNode.next; } else { unblockOtherNode(waiter, executor); return; } } } private void unblockAccept(@Nonnull final Consumer consumer, @Nonnull Executor executor, @Nonnull InternalCompletableFuture future) { final Object value = resolve(state); if (cascadeException(value, future)) { return; } try { executor.execute(() -> { try { consumer.accept((V) value); future.complete(null); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } } /** * @param waiter the current wait node, see javadoc of {@link #state state field} * @param executor the {@link Executor} on which to execute the action associated with {@code waiter} */ @SuppressWarnings("checkstyle:CyclomaticComplexity") protected void unblockOtherNode(Object waiter, Executor executor) { if (!(waiter instanceof Waiter)) { return; } Object value = resolve(state); if (waiter instanceof UniWaiter) { ((UniWaiter) waiter).execute(executor, value); } else if (waiter instanceof BiWaiter) { Throwable t = (value instanceof ExceptionalResult) ? ((ExceptionalResult) value).cause : null; value = (value instanceof ExceptionalResult) ? null : value; ((BiWaiter) waiter).execute(executor, value, t); } else if (waiter instanceof ExceptionallyNode) { ((ExceptionallyNode) waiter).execute(value); } } protected abstract Exception wrapToInstanceNotActiveException(RejectedExecutionException e); protected V returnOrThrowWithJoinConventions(Object resolved) { if (!(resolved instanceof ExceptionalResult)) { return (V) resolved; } Throwable cause = ((ExceptionalResult) resolved).cause; if (cause instanceof CancellationException) { throw (CancellationException) cause; } else if (cause instanceof CompletionException) { throw (CompletionException) cause; } throw new CompletionException(cause); } /** * @param value the resolved state of this future * @return an {@link ExceptionalResult} wrapping a {@link Throwable} in case value is resolved * to an exception, or the normal completion value. Subclasses may choose to treat * specific normal completion values in a special way (eg deserialize when the completion * value is an instance of {@code Data}. */ protected Object resolve(Object value) { return value; } protected ExceptionalResult toExceptionalResult(Object object) { assert object instanceof ExceptionalResult; return (ExceptionalResult) object; } protected V resolveAndThrowWithJoinConvention(Object state) { Object value = resolve(state); return returnOrThrowWithJoinConventions(value); } protected void unblockApply(@Nonnull final Function function, @Nonnull Executor executor, @Nonnull InternalCompletableFuture future) { final Object value = resolve(state); if (cascadeException(value, future)) { return; } try { executor.execute(() -> { try { U result = function.apply((V) value); future.complete(result); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } } protected void unblockRun(@Nonnull final Runnable runnable, @Nonnull Executor executor, @Nonnull CompletableFuture future) { final Object value = resolve(state); if (cascadeException(value, future)) { return; } runAfter0(future, runnable, executor); } protected void unblockHandle(@Nonnull BiFunction fn, @Nonnull Executor executor, @Nonnull CompletableFuture future) { final Object result = resolve(state); V value; Throwable throwable; if (result instanceof ExceptionalResult) { throwable = ((ExceptionalResult) result).getCause(); value = null; } else { throwable = null; value = (V) result; } try { executor.execute(() -> { try { U r = fn.apply(value, throwable); future.complete(r); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } } protected void unblockWhenComplete(@Nonnull final BiConsumer biConsumer, @Nonnull Executor executor, @Nonnull CompletableFuture future) { Object result = resolve(state); V value; Throwable throwable; if (result instanceof ExceptionalResult) { throwable = ((ExceptionalResult) result).cause; value = null; } else { throwable = null; value = (V) result; } try { executor.execute(() -> { try { biConsumer.accept(value, throwable); } catch (Throwable t) { completeDependentExceptionally(future, throwable, t); return; } completeDependent(future, value, throwable); }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } } private void unblockExceptionally(@Nonnull Function fn, InternalCompletableFuture future) { Object result = resolve(state); if (result instanceof ExceptionalResult) { Throwable throwable = ((ExceptionalResult) result).cause; try { V value = fn.apply(throwable); future.complete(value); } catch (Throwable t) { future.completeExceptionally(t); } } else { future.complete((V) result); } } protected void unblockCompose(@Nonnull final Function> function, @Nonnull Executor executor, @Nonnull CompletableFuture future) { Object result = resolve(state); if (cascadeException(result, future)) { return; } final V res = (V) result; try { executor.execute(() -> { try { CompletionStage r = function.apply(res); r.whenCompleteAsync((v, t) -> { if (t == null) { future.complete(v); } else { future.completeExceptionally(t); } }, CALLER_RUNS); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } } @SuppressWarnings("checkstyle:npathcomplexity") protected void unblockCombine(@Nonnull CompletionStage other, @Nonnull final BiFunction function, @Nonnull Executor executor, @Nonnull InternalCompletableFuture future) { Object result = resolve(state); final CompletableFuture otherFuture = (other instanceof CompletableFuture) ? (CompletableFuture) other : other.toCompletableFuture(); // CompletionStage#thenCombine specifies to wait both futures for normal completion, // but does not specify that it is required to wait for both when completed exceptionally. // The CompletableFuture#thenCombine implementation actually waits both future completion // even when one of them is completed exceptionally. // In case this future is completed exceptionally, the result is also exceptionally // completed without checking whether otherFuture is completed or not if (cascadeException(result, future)) { return; } final V value = (V) result; if (!otherFuture.isDone()) { // register on other future as waiter and return otherFuture.whenCompleteAsync((v, t) -> { if (t != null) { future.completeExceptionally(t); } try { R r = function.apply(value, v); future.complete(r); } catch (Throwable e) { future.completeExceptionally(e); } }, executor); return; } // both futures are done if (otherFuture.isCompletedExceptionally()) { otherFuture.whenCompleteAsync((v, t) -> future.completeExceptionally(t), CALLER_RUNS); return; } U otherValue = otherFuture.join(); try { executor.execute(() -> { try { R r = function.apply(value, otherValue); future.complete(r); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } } @SuppressWarnings("checkstyle:npathcomplexity") private void unblockAcceptBoth(@Nonnull CompletableFuture otherFuture, @Nonnull final BiConsumer action, @Nonnull Executor executor, @Nonnull InternalCompletableFuture future) { final Object value = resolve(state); // in case this future is completed exceptionally, the result is also exceptionally completed // without checking whether otherFuture is completed or not if (cascadeException(value, future)) { return; } if (!otherFuture.isDone()) { // register on other future as waiter and return otherFuture.whenCompleteAsync((u, t) -> { if (t != null) { future.completeExceptionally(t); } try { action.accept((V) value, u); future.complete(null); } catch (Throwable e) { future.completeExceptionally(e); } }, executor); return; } // both futures are done if (otherFuture.isCompletedExceptionally()) { otherFuture.whenCompleteAsync((v, t) -> future.completeExceptionally(t), CALLER_RUNS); return; } U otherValue = otherFuture.join(); try { executor.execute(() -> { try { action.accept((V) value, otherValue); future.complete(null); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } } private void unblockRunAfterBoth(@Nonnull CompletableFuture otherFuture, @Nonnull final Runnable action, @Nonnull Executor executor, @Nonnull CompletableFuture future) { Object result = resolve(state); // in case this future is completed exceptionally, the result is also exceptionally completed // without checking whether otherFuture is completed or not if (cascadeException(result, future)) { return; } if (!otherFuture.isDone()) { // register on other future as waiter and return otherFuture.whenCompleteAsync((u, t) -> { if (t != null) { future.completeExceptionally(t); } try { action.run(); future.complete(null); } catch (Throwable e) { future.completeExceptionally(e); } }, executor); return; } // both futures are done if (otherFuture.isCompletedExceptionally()) { otherFuture.whenCompleteAsync((v, t) -> future.completeExceptionally(t), CALLER_RUNS); return; } runAfter0(future, action, executor); } protected void unblockApplyToEither(@Nonnull final Function action, @Nonnull Executor executor, @Nonnull CompletableFuture future) { Object result = resolve(state); if (cascadeException(result, future)) { return; } applyTo0(future, action, executor, (V) result); } protected void unblockAcceptEither(@Nonnull final Consumer action, @Nonnull Executor executor, @Nonnull CompletableFuture future) { Object result = resolve(state); if (cascadeException(result, future)) { return; } acceptAfter0(future, action, executor, (V) result); } protected CompletableFuture unblockRunAfterEither(@Nonnull final Runnable action, @Nonnull Executor executor, @Nonnull CompletableFuture future) { Object result = resolve(state); if (cascadeException(result, future)) { return future; } return runAfter0(future, action, executor); } /** * Registers a waiter (thread/ExecutionCallback) that gets notified when * the future completes. * * @param waiter the waiter * @param executor the {@link Executor} to use in case of an * {@link ExecutionCallback}. * @return UNRESOLVED if the registration was a success, anything else but void * is the response. */ private Object registerWaiter(Object waiter, Executor executor) { assert !(waiter instanceof UnblockableThread) : "Waiting for response on this thread is illegal"; WaitNode waitNode = null; for (; ; ) { final Object oldState = state; if (isDone(oldState)) { return oldState; } Object newState; if (oldState == UNRESOLVED && (executor == null || executor == defaultExecutor())) { // nothing is syncing on this future, so instead of creating a WaitNode, we just try to cas the waiter newState = waiter; } else { // something already has been registered for syncing, so we need to create a WaitNode if (waitNode == null) { waitNode = new WaitNode(waiter, executor); } waitNode.next = oldState; newState = waitNode; } if (compareAndSetState(oldState, newState)) { // we have successfully registered return UNRESOLVED; } } } void unregisterWaiter(Thread waiter) { WaitNode prev = null; Object current = state; while (current != null) { Object currentWaiter = current.getClass() == WaitNode.class ? ((WaitNode) current).waiter : current; Object next = current.getClass() == WaitNode.class ? ((WaitNode) current).next : null; if (currentWaiter == waiter) { // it is the item we are looking for, so lets try to remove it if (prev == null) { // it's the first item of the stack, so we need to change the head to the next Object n = next == null ? UNRESOLVED : next; // if we manage to CAS we are done, else we need to restart current = compareAndSetState(current, n) ? null : state; } else { // remove the current item (this is done by letting the prev.next point to the next instead of current) prev.next = next; // end the loop current = null; } } else { // it isn't the item we are looking for, so lets move on to the next prev = current.getClass() == WaitNode.class ? (WaitNode) current : null; current = next; } } } /** * Can be called multiple times, but only the first answer will lead to the * future getting triggered. All subsequent complete calls are ignored. * * @param value The type of response to offer. * @return true if offered response, either a final response or an * internal response, is set/applied, false otherwise. If false * is returned, that means offered response is ignored because a final response * is already set to this future. */ @Override public final boolean complete(Object value) { return complete0(value); } public final boolean completeExceptionallyInternal(Object value) { return complete0(wrapThrowable(value)); } private boolean complete0(Object value) { for (; ; ) { final Object oldState = state; if (isDone(oldState)) { warnIfSuspiciousDoubleCompletion(oldState, value); return false; } if (compareAndSetState(oldState, value)) { onComplete(); unblockAll(oldState, defaultExecutor()); return true; } } } protected void onComplete() { if (isCompletedExceptionally()) { super.completeExceptionally(toExceptionalResult(state).getCause()); } else { super.complete((V) state); } } // it can be that this future is already completed, e.g. when an invocation already // received a response, but before it cleans up itself, it receives a HazelcastInstanceNotActiveException private void warnIfSuspiciousDoubleCompletion(Object s0, Object s1) { if (s0 != s1 && !(isStateCancelled(s0)) && !(isStateCancelled(s1))) { String message = String.format("Future.complete(Object) on completed future. " + "Request: %s, current value: %s, offered value: %s", invocationToString(), s0, s1); logger.warning(message, new Exception(message)); } } @Override public String toString() { Object state = getState(); if (isDone(state)) { return "InvocationFuture{invocation=" + invocationToString() + ", value=" + state + '}'; } else { return "InvocationFuture{invocation=" + invocationToString() + ", done=false}"; } } private CompletableFuture runAfter0(@Nonnull CompletableFuture result, @Nonnull Runnable action, @Nonnull Executor executor) { try { executor.execute(() -> { try { action.run(); result.complete(null); } catch (Throwable t) { result.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { result.completeExceptionally(wrapToInstanceNotActiveException(e)); } return result; } private CompletableFuture acceptAfter0(@Nonnull CompletableFuture result, @Nonnull Consumer consumer, @Nonnull Executor executor, V value) { try { executor.execute(() -> { try { consumer.accept(value); result.complete(null); } catch (Throwable t) { result.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { result.completeExceptionally(wrapToInstanceNotActiveException(e)); } return result; } private CompletableFuture applyTo0(@Nonnull CompletableFuture future, @Nonnull Function consumer, @Nonnull Executor executor, V value) { try { executor.execute(() -> { try { future.complete(consumer.apply(value)); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); } return future; } InternalCompletableFuture newCompletableFuture() { return new InternalCompletableFuture<>(); } /** * If {@code resolved} is an {@link ExceptionalResult}, complete the {@code dependent} * exceptionally with a {@link CompletionException} that wraps the cause. * Used as discussed in {@link CompletionStage} javadoc regarding exceptional completion * of dependents. * * @param resolved a resolved state, as returned from {@link #resolve(Object)} * @param dependent a dependent {@link CompletableFuture} * @return {@code true} in case the dependent was completed exceptionally, otherwise {@code false} */ private static boolean cascadeException(Object resolved, CompletableFuture dependent) { if (resolved instanceof ExceptionalResult) { dependent.completeExceptionally(wrapInCompletionException((((ExceptionalResult) resolved).cause))); return true; } return false; } private static CompletionException wrapInCompletionException(Throwable t) { return (t instanceof CompletionException) ? (CompletionException) t : new CompletionException(t); } protected static ExceptionalResult wrapThrowable(Object value) { if (value instanceof ExceptionalResult) { return (ExceptionalResult) value; } return new ExceptionalResult((Throwable) value); } /** * Linked nodes to record waiting {@link Thread} or {@link ExecutionCallback} * instances using a Treiber stack. *

* A waiter is something that gets triggered when a response comes in. There * are 2 types of waiters: *

    *
  1. Thread: when a future.get is done.
  2. *
  3. ExecutionCallback: when a future.andThen is done
  4. *
* The waiter is either a Thread or an ExecutionCallback. *

* The {@link WaitNode} is effectively immutable. Once the WaitNode is set in * the 'state' field, it will not be modified. Also updating the state, * introduces a happens before relation so the 'next' field can be read safely. */ static final class WaitNode { final Object waiter; volatile Object next; private final @Nonnull Executor executor; WaitNode(Object waiter, @Nullable Executor executor) { this.waiter = waiter; this.executor = executor == null ? ConcurrencyUtil.getDefaultAsyncExecutor() : executor; } @Override public String toString() { return "WaitNode{" + "waiter=" + waiter + ", next=" + next + ", executor=" + executor + '}'; } } public static final class ExceptionalResult { private final Throwable cause; public ExceptionalResult(Throwable cause) { this.cause = cause; } public Throwable getCause() { return cause; } /** * Wraps the {@link #cause} so that the remote/async throwable is not lost, * however is delivered as the cause to an throwable with a local stack trace * that makes sense to user code that is synchronizing on {@code joinInternal()}. *

* Exception wrapping rules: *

    *
  • * if cause is an instance of {@link RuntimeException} then the cause is cloned * The clone throwable has the local stack trace merged into to the original stack trace *
  • *
  • * if cause is an instance of {@link ExecutionException} or {@link InvocationTargetException} * with a non-null cause, then unwrap and apply the rules for the cause *
  • *
  • * if cause is an {@link Error}, then the cause is cloned. * The clone throwable has the local stack trace merged into to the original stack trace *
  • *
  • * otherwise, wrap cause in a {@link HazelcastException} reporting the local stack trace, * while the remote stack trace is reported in its cause exception. *
  • *
* * @return */ public Throwable wrapForJoinInternal() { return wrapOrPeel(cause); } @Override public String toString() { return "ExceptionalResult{" + "cause=" + cause + '}'; } } /** * Marker interface for completions registered on a yet incomplete future. * Also extends {@link java.util.concurrent.CompletableFuture.AsynchronousCompletionTask} * for monitoring & debugging. */ interface Waiter extends AsynchronousCompletionTask { } /** * Interface for dependent stages registered on a yet incomplete future * which perform some action ({@code Function}, {@code Consumer}...) on this * future's resolved value. */ interface UniWaiter extends Waiter { void execute(@Nonnull Executor executor, Object value); } /** * Interface for dependent stages registered on a yet incomplete future * which perform some action ({@code BiFunction}, {@code BiConsumer}..) * on the normal or exceptional completion value. */ interface BiWaiter extends Waiter { void execute(@Nonnull Executor executor, V value, T throwable); } // a WaitNode for a Function protected final class ApplyNode implements UniWaiter { final CompletableFuture future; final Function function; ApplyNode(CompletableFuture future, Function function) { this.future = future; this.function = function; } @Override public void execute(@Nonnull Executor executor, Object value) { if (cascadeException(value, future)) { return; } try { executor.execute(() -> { try { future.complete(function.apply((V) value)); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException e) { future.completeExceptionally(wrapToInstanceNotActiveException(e)); throw e; } } } // a WaitNode for exceptionally(Function) protected static final class ExceptionallyNode implements Waiter { final CompletableFuture future; final Function function; ExceptionallyNode(CompletableFuture future, Function function) { this.future = future; this.function = function; } public void execute(Object resolved) { if (resolved instanceof ExceptionalResult) { Throwable throwable = ((ExceptionalResult) resolved).cause; try { R value = function.apply(throwable); future.complete(value); } catch (Throwable t) { future.completeExceptionally(t); } } else { future.complete((R) resolved); } } } // a WaitNode for a BiFunction private final class HandleNode implements BiWaiter { final CompletableFuture future; final BiFunction biFunction; HandleNode(CompletableFuture future, BiFunction biFunction) { this.future = future; this.biFunction = biFunction; } @Override public void execute(@Nonnull Executor executor, V value, Throwable throwable) { try { executor.execute(() -> { try { future.complete(biFunction.apply(value, throwable)); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException exception) { future.completeExceptionally(wrapToInstanceNotActiveException(exception)); throw exception; } } } // a WaitNode for a BiConsumer private final class WhenCompleteNode implements BiWaiter { final CompletableFuture future; final BiConsumer biConsumer; WhenCompleteNode(@Nonnull CompletableFuture future, @Nonnull BiConsumer biConsumer) { this.future = future; this.biConsumer = biConsumer; } @Override public void execute(@Nonnull Executor executor, V value, T throwable) { try { executor.execute(() -> { try { biConsumer.accept(value, throwable); } catch (Throwable t) { completeDependentExceptionally(future, throwable, t); return; } complete(value, throwable); }); } catch (RejectedExecutionException exception) { future.completeExceptionally(wrapToInstanceNotActiveException(exception)); throw exception; } } private void complete(V value, T throwable) { if (throwable == null) { future.complete(value); } else { future.completeExceptionally(throwable); } } } // a WaitNode for a Consumer private final class AcceptNode implements UniWaiter { final CompletableFuture future; final Consumer consumer; AcceptNode(@Nonnull CompletableFuture future, @Nonnull Consumer consumer) { this.future = future; this.consumer = consumer; } @Override public void execute(@Nonnull Executor executor, Object value) { if (cascadeException(value, future)) { return; } try { executor.execute(() -> { try { consumer.accept((T) value); future.complete(null); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException exception) { future.completeExceptionally(wrapToInstanceNotActiveException(exception)); throw exception; } } } // a WaitNode for a Runnable protected final class RunNode implements UniWaiter { final CompletableFuture future; final Runnable runnable; RunNode(@Nonnull CompletableFuture future, @Nonnull Runnable runnable) { this.future = future; this.runnable = runnable; } @Override public void execute(@Nonnull Executor executor, Object resolved) { if (cascadeException(resolved, future)) { return; } try { executor.execute(() -> { try { runnable.run(); future.complete(null); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException exception) { future.completeExceptionally(wrapToInstanceNotActiveException(exception)); throw exception; } } } protected final class ComposeNode implements UniWaiter { final CompletableFuture future; final Function> function; ComposeNode(CompletableFuture future, Function> function) { this.future = future; this.function = function; } @Override public void execute(@Nonnull Executor executor, Object resolved) { if (cascadeException(resolved, future)) { return; } try { executor.execute(() -> { try { CompletionStage r = function.apply((T) resolved); r.whenCompleteAsync((v, t) -> { if (t == null) { future.complete(v); } else { future.completeExceptionally(t); } }, CALLER_RUNS); } catch (Throwable t) { future.completeExceptionally(t); } }); } catch (RejectedExecutionException exception) { future.completeExceptionally(wrapToInstanceNotActiveException(exception)); throw exception; } } } // common superclass of waiters for two futures (combine, acceptBoth, runAfterBoth) protected abstract class AbstractBiNode implements UniWaiter { final CompletableFuture result; final CompletableFuture otherFuture; final AtomicBoolean executed; AbstractBiNode(CompletableFuture future, CompletableFuture otherFuture) { this.result = future; this.otherFuture = otherFuture; this.executed = new AtomicBoolean(); } @Override @SuppressWarnings("checkstyle:npathcomplexity") public void execute(@Nonnull Executor executor, Object resolved) { if (cascadeException(resolved, result)) { return; } if (!otherFuture.isDone()) { // register on other future and exit otherFuture.whenCompleteAsync((u, t) -> { if (!executed.compareAndSet(false, true)) { return; } if (t != null) { result.completeExceptionally(t); } try { R r = process((T) resolved, u); result.complete(r); } catch (Throwable e) { result.completeExceptionally(e); } }, executor); return; } if (!executed.compareAndSet(false, true)) { return; } if (otherFuture.isCompletedExceptionally()) { otherFuture.exceptionally(t -> { result.completeExceptionally(t); return null; }); return; } U otherValue = otherFuture.join(); try { executor.execute(() -> { try { R r = process((T) resolved, otherValue); result.complete(r); } catch (Throwable t) { result.completeExceptionally(t); } }); } catch (RejectedExecutionException exception) { result.completeExceptionally(wrapToInstanceNotActiveException(exception)); throw exception; } } abstract R process(T t, U u); } private final class CombineNode extends AbstractBiNode { final BiFunction function; CombineNode(CompletableFuture future, CompletableFuture otherFuture, BiFunction function) { super(future, otherFuture); this.function = function; } @Override R process(T t, U u) { return function.apply(t, u); } } private final class AcceptBothNode extends AbstractBiNode { final BiConsumer action; AcceptBothNode(CompletableFuture future, CompletableFuture otherFuture, BiConsumer action) { super(future, otherFuture); this.action = action; } @Override Void process(T t, U u) { action.accept(t, u); return null; } } private final class RunAfterBothNode extends AbstractBiNode { final Runnable action; RunAfterBothNode(CompletableFuture future, CompletableFuture otherFuture, Runnable action) { super(future, otherFuture); this.action = action; } @Override Void process(T t, U u) { action.run(); return null; } } // common superclass of waiters for either of two futures (applyEither, acceptEither, runAfterEither) protected abstract class AbstractEitherNode implements UniWaiter, BiConsumer { final CompletableFuture result; final AtomicBoolean executed; AbstractEitherNode(CompletableFuture future) { this.result = future; this.executed = new AtomicBoolean(); } @Override public void execute(@Nonnull Executor executor, Object resolved) { if (!executed.compareAndSet(false, true)) { return; } if (cascadeException(resolved, result)) { return; } try { executor.execute(() -> { try { R r = process((T) resolved); result.complete(r); } catch (Throwable t) { result.completeExceptionally(t); } }); } catch (RejectedExecutionException exception) { result.completeExceptionally(wrapToInstanceNotActiveException(exception)); throw exception; } } @Override public void accept(T t, Throwable throwable) { if (!executed.compareAndSet(false, true)) { return; } if (throwable != null) { result.completeExceptionally(throwable); } try { R r = process(t); result.complete(r); } catch (Throwable e) { result.completeExceptionally(e); } } abstract R process(T t); } private final class RunAfterEither extends AbstractEitherNode { final Runnable action; RunAfterEither(CompletableFuture future, Runnable action) { super(future); this.action = action; } @Override Void process(T t) { action.run(); return null; } } private final class AcceptEither extends AbstractEitherNode { final Consumer action; AcceptEither(CompletableFuture future, Consumer action) { super(future); this.action = action; } @Override Void process(T t) { action.accept(t); return null; } } private final class ApplyEither extends AbstractEitherNode { final Function action; ApplyEither(CompletableFuture future, Function action) { super(future); this.action = action; } @Override R process(T t) { return action.apply(t); } } private static boolean isStateCancelled(final Object state) { return ((state instanceof ExceptionalResult) && (((ExceptionalResult) state).cause instanceof CancellationException)); } /** * Completes dependent {@code future} exceptionally. When the parent future was completed exceptionally, * then dependent future is also completed exceptionally with a {@link CompletionException} wrapping * {@code exceptionFromParent}. Otherwise, the dependent future is completed exceptionally with * the exception thrown from user action ({@code exceptionFromAction}). */ private static void completeDependentExceptionally(CompletableFuture future, Throwable exceptionFromParent, Throwable exceptionFromAction) { assert (exceptionFromParent != null || exceptionFromAction != null); if (exceptionFromParent == null) { future.completeExceptionally(exceptionFromAction); } else { future.completeExceptionally(wrapInCompletionException(exceptionFromParent)); } } /** * Completes dependent future {@code future} with the given {@code throwable} wrapped in * {@code CompletionException}, if {@code throwable} is not {@code null}, or with the given * {@code value}. */ private static void completeDependent(CompletableFuture future, V value, Throwable throwable) { if (throwable == null) { future.complete(value); } else { future.completeExceptionally(wrapInCompletionException(throwable)); } } private static boolean isDone(final Object state) { if (state == null) { return true; } return !(state == UNRESOLVED || state instanceof WaitNode || state instanceof Thread || state instanceof ExecutionCallback || state instanceof Waiter); } private static void restoreInterrupt(boolean interrupted) { if (interrupted) { Thread.currentThread().interrupt(); } } static Throwable wrapOrPeel(Throwable cause) { if (cause instanceof RuntimeException) { return wrapRuntimeException((RuntimeException) cause); } if ((cause instanceof ExecutionException || cause instanceof InvocationTargetException) && cause.getCause() != null) { return wrapOrPeel(cause.getCause()); } if (cause instanceof Error) { if (cause instanceof OutOfMemoryError) { OutOfMemoryErrorDispatcher.onOutOfMemory((OutOfMemoryError) cause); } return wrapError((Error) cause); } return new HazelcastException(cause); } private static RuntimeException wrapRuntimeException(RuntimeException cause) { if (cause instanceof WrappableException) { return ((WrappableException) cause).wrap(); } return cloneExceptionWithFixedAsyncStackTrace(cause); } private static Error wrapError(Error cause) { return cloneExceptionWithFixedAsyncStackTrace(cause); } }