
io.netty5.util.concurrent.DefaultPromise Maven / Gradle / Ivy
/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you 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:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty5.util.concurrent;
import io.netty5.util.internal.StringUtil;
import io.netty5.util.internal.ThrowableUtil;
import io.netty5.util.internal.logging.InternalLogger;
import io.netty5.util.internal.logging.InternalLoggerFactory;
import java.util.concurrent.CancellationException;
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.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import static java.util.Objects.requireNonNull;
public class DefaultPromise implements Promise, Future,
FutureCompletionStage, java.util.concurrent.Future {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultPromise.class);
private static final InternalLogger rejectedExecutionLogger =
InternalLoggerFactory.getInstance(DefaultPromise.class.getName() + ".rejectedExecution");
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater RESULT_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(DefaultPromise.class, Object.class, "result");
private static final Object SUCCESS = new Object();
private static final Object UNCANCELLABLE = new Object();
private static final CauseHolder CANCELLATION_CAUSE_HOLDER = new CauseHolder(
StacklessCancellationException.newInstance(DefaultPromise.class, "cancel(...)"));
private static final StackTraceElement[] CANCELLATION_STACK = CANCELLATION_CAUSE_HOLDER.cause.getStackTrace();
static final Object NULL_CONTEXT = new Object();
private volatile Object result;
private final EventExecutor executor;
/**
* One or more listeners. Can be a {@link FutureListener} or a {@link DefaultFutureListeners}.
* If {@code null}, it means either 1) no listeners were added yet or 2) all listeners were notified.
*
* Note that if a {@link FutureContextListener} is added, we immediately upgrade to a {@link DefaultFutureListeners}
* as we otherwise wouldn't have room to store the associated context object.
*
* Threading - synchronized(this). We must support adding listeners when there is no EventExecutor.
*/
private Object listeners;
/**
* Threading - synchronized(this). We are required to hold the monitor to use Java's underlying wait()/notifyAll().
*/
private short waiters;
/**
* Creates a new unfulfilled promise.
*
* This constructor is only meant to be used by sub-classes.
*
* @param executor
* The {@link EventExecutor} which is used to notify the promise once it is complete.
* It is assumed this executor will protect against {@link StackOverflowError} exceptions.
* The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
* depth exceeds a threshold.
*/
protected DefaultPromise(EventExecutor executor) {
this.executor = requireNonNull(executor, "executor");
}
/**
* Creates a new promise that has already been completed successfully.
*
* @param executor
* The {@link EventExecutor} which is used to notify the promise once it is complete.
* It is assumed this executor will protect against {@link StackOverflowError} exceptions.
* The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
* depth exceeds a threshold.
* @param result The result of the successful promise.
*/
static Promise newSuccessfulPromise(EventExecutor executor, V result) {
return new DefaultPromise<>(executor, result);
}
/**
* Creates a new promise that has already failed.
*
* @param executor
* The {@link EventExecutor} which is used to notify the promise once it is complete.
* It is assumed this executor will protect against {@link StackOverflowError} exceptions.
* The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
* depth exceeds a threshold.
* @param cause The {@link Throwable} that caused the failure of the returned promise.
*/
static Promise newFailedPromise(EventExecutor executor, Throwable cause) {
return new DefaultPromise<>(cause, executor);
}
private DefaultPromise(EventExecutor executor, Object result) {
this.executor = requireNonNull(executor, "executor");
this.result = result == null? SUCCESS : result;
}
private DefaultPromise(Throwable cause, EventExecutor executor) {
this.executor = requireNonNull(executor, "executor");
result = new CauseHolder(requireNonNull(cause, "cause"));
}
@Override
public Promise setSuccess(V result) {
if (setSuccess0(result)) {
return this;
}
throw new IllegalStateException("complete already: " + this);
}
@Override
public boolean trySuccess(V result) {
return setSuccess0(result);
}
@Override
public Promise setFailure(Throwable cause) {
if (setFailure0(cause)) {
return this;
}
throw new IllegalStateException("complete already: " + this, cause);
}
@Override
public boolean tryFailure(Throwable cause) {
return setFailure0(cause);
}
@Override
public boolean setUncancellable() {
return RESULT_UPDATER.compareAndSet(this, null, UNCANCELLABLE);
}
@Override
public Future asFuture() {
return this;
}
@Override
public boolean isSuccess() {
Object result = this.result;
return result != null && result != UNCANCELLABLE && !(result instanceof CauseHolder);
}
@Override
public boolean isFailed() {
return result instanceof CauseHolder;
}
@Override
public boolean isCancellable() {
return result == null;
}
private static final class LeanCancellationException extends CancellationException {
private static final long serialVersionUID = 2794674970981187807L;
// Suppress a warning since the method doesn't need synchronization
@Override
public Throwable fillInStackTrace() { // lgtm[java/non-sync-override]
setStackTrace(CANCELLATION_STACK);
return this;
}
@Override
public String toString() {
return CancellationException.class.getName();
}
}
@Override
public Throwable cause() {
return cause0(result);
}
private Throwable cause0(Object result) {
if (!isDone0(result)) {
throw new IllegalStateException("Cannot call cause() on a future that has not completed.");
}
if (!(result instanceof CauseHolder)) {
return null;
}
if (result == CANCELLATION_CAUSE_HOLDER) {
CancellationException ce = new LeanCancellationException();
if (RESULT_UPDATER.compareAndSet(this, CANCELLATION_CAUSE_HOLDER, new CauseHolder(ce))) {
return ce;
}
result = this.result;
}
return ((CauseHolder) result).cause;
}
@Override
public Future addListener(FutureListener super V> listener) {
requireNonNull(listener, "listener");
addListener0(listener, null);
if (isDone()) {
notifyListeners();
}
return this;
}
@Override
public Future addListener(C context, FutureContextListener super C, ? super V> listener) {
requireNonNull(listener, "listener");
addListener0(listener, context == null ? NULL_CONTEXT : context);
if (isDone()) {
notifyListeners();
}
return this;
}
@Override
public FutureCompletionStage await() throws InterruptedException {
if (isDone()) {
return this;
}
if (Thread.interrupted()) {
throw new InterruptedException(toString());
}
checkDeadLock();
synchronized (this) {
while (!isDone()) {
incWaiters();
try {
wait();
} finally {
decWaiters();
}
}
}
return this;
}
@Override
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return await0(unit.toNanos(timeout), true);
}
@SuppressWarnings("unchecked")
@Override
public V getNow() {
Object result = this.result;
if (!isDone0(result)) {
throw new IllegalStateException("Cannot call getNow() on a future that has not completed.");
}
if (result instanceof CauseHolder || result == SUCCESS) {
return null;
}
return (V) result;
}
@SuppressWarnings("unchecked")
@Override
public V get() throws InterruptedException, ExecutionException {
Object result = this.result;
if (!isDone0(result)) {
await();
result = this.result;
}
if (result == SUCCESS || result == UNCANCELLABLE) {
return null;
}
Throwable cause = cause0(result);
if (cause == null) {
return (V) result;
}
if (cause instanceof CancellationException) {
throw (CancellationException) cause;
}
throw new ExecutionException(cause);
}
@SuppressWarnings("unchecked")
@Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
Object result = this.result;
if (!isDone0(result)) {
if (!await(timeout, unit)) {
throw new TimeoutException();
}
result = this.result;
}
if (result == SUCCESS || result == UNCANCELLABLE) {
return null;
}
Throwable cause = cause0(result);
if (cause == null) {
return (V) result;
}
if (cause instanceof CancellationException) {
throw (CancellationException) cause;
}
throw new ExecutionException(cause);
}
@Override
public boolean cancel() {
if (RESULT_UPDATER.compareAndSet(this, null, CANCELLATION_CAUSE_HOLDER)) {
if (checkNotifyWaiters()) {
notifyListeners();
}
return true;
}
return false;
}
@Override
public boolean isCancelled() {
return isCancelled0(result);
}
@Override
public boolean isDone() {
return isDone0(result);
}
@Override
public FutureCompletionStage sync() throws InterruptedException {
await();
rethrowIfFailed();
return this;
}
@Override
public String toString() {
return toStringBuilder().toString();
}
protected StringBuilder toStringBuilder() {
StringBuilder buf = new StringBuilder(64)
.append(StringUtil.simpleClassName(this))
.append('@')
.append(Integer.toHexString(hashCode()));
Object result = this.result;
if (result == SUCCESS) {
buf.append("(success)");
} else if (result == UNCANCELLABLE) {
buf.append("(uncancellable)");
} else if (result instanceof CauseHolder) {
buf.append("(failure: ")
.append(((CauseHolder) result).cause)
.append(')');
} else if (result != null) {
buf.append("(success: ")
.append(result)
.append(')');
} else {
buf.append("(incomplete)");
}
return buf;
}
/**
* Get the executor used to notify listeners when this promise is complete.
*
* It is assumed this executor will protect against {@link StackOverflowError} exceptions.
* The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
* depth exceeds a threshold.
* @return The executor used to notify listeners when this promise is complete.
*/
@Override
public final EventExecutor executor() {
return executor;
}
protected void checkDeadLock() {
checkDeadLock(executor);
}
protected final void checkDeadLock(EventExecutor executor) {
if (executor.inEventLoop()) {
throw new BlockingOperationException(toString());
}
}
private void notifyListeners() {
safeExecute(executor(), new NotifyListeners(this));
}
private static final class NotifyListeners implements Runnable {
private final DefaultPromise> promise;
private NotifyListeners(DefaultPromise> promise) {
this.promise = promise;
}
@Override
public void run() {
promise.notifyListenersNow();
}
}
@SuppressWarnings({ "unchecked", "MethodOnlyUsedFromInnerClass" })
private void notifyListenersNow() {
Object listeners;
synchronized (this) {
// Only proceed if there are listeners to notify.
if (this.listeners == null) {
return;
}
listeners = this.listeners;
this.listeners = null;
}
for (;;) {
if (listeners instanceof DefaultFutureListeners) {
notifyListeners0((DefaultFutureListeners) listeners);
} else {
notifyListener0(this, (FutureListener) listeners);
}
synchronized (this) {
if (this.listeners == null) {
return;
}
listeners = this.listeners;
this.listeners = null;
}
}
}
private void notifyListeners0(DefaultFutureListeners listeners) {
listeners.notifyListeners(this, logger);
}
static void notifyListener0(Future future, FutureListener super V> l) {
try {
l.operationComplete(future);
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
}
}
}
private synchronized void addListener0(Object listener, Object context) {
if (listeners == null && context == null) {
listeners = listener;
} else if (listeners instanceof DefaultFutureListeners) {
((DefaultFutureListeners) listeners).add(listener, context);
} else {
DefaultFutureListeners listeners = new DefaultFutureListeners();
if (this.listeners != null) {
listeners.add(this.listeners, null);
}
listeners.add(listener, context);
this.listeners = listeners;
}
}
private boolean setSuccess0(V result) {
return setValue0(result == null ? SUCCESS : result);
}
private boolean setFailure0(Throwable cause) {
return setValue0(new CauseHolder(requireNonNull(cause, "cause")));
}
private boolean setValue0(Object objResult) {
if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
if (checkNotifyWaiters()) {
notifyListeners();
}
return true;
}
return false;
}
/**
* Check if there are any waiters and if so notify these.
* @return {@code true} if there are any listeners attached to the promise, {@code false} otherwise.
*/
private synchronized boolean checkNotifyWaiters() {
if (waiters > 0) {
notifyAll();
}
return listeners != null;
}
private void incWaiters() {
if (waiters == Short.MAX_VALUE) {
throw new IllegalStateException("too many waiters: " + this);
}
++waiters;
}
private void decWaiters() {
--waiters;
}
private void rethrowIfFailed() {
Throwable cause = cause();
if (cause == null) {
return;
}
if (cause instanceof CancellationException) {
throw (CancellationException) cause;
}
throw new CompletionException(cause);
}
private boolean await0(long timeoutNanos, boolean interruptable) throws InterruptedException {
if (isDone()) {
return true;
}
if (timeoutNanos <= 0) {
return isDone();
}
if (interruptable && Thread.interrupted()) {
throw new InterruptedException(toString());
}
checkDeadLock();
// Start counting time from here instead of the first line of this method,
// to avoid/postpone performance cost of System.nanoTime().
final long startTime = System.nanoTime();
synchronized (this) {
boolean interrupted = false;
try {
long waitTime = timeoutNanos;
while (!isDone() && waitTime > 0) {
incWaiters();
try {
wait(waitTime / 1000000, (int) (waitTime % 1000000));
} catch (InterruptedException e) {
if (interruptable) {
throw e;
} else {
interrupted = true;
}
} finally {
decWaiters();
}
// Check isDone() in advance, try to avoid calculating the elapsed time later.
if (isDone()) {
return true;
}
// Calculate the elapsed time here instead of in the while condition,
// try to avoid performance cost of System.nanoTime() in the first loop of while.
waitTime = timeoutNanos - (System.nanoTime() - startTime);
}
return isDone();
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
}
private static boolean isCancelled0(Object result) {
return result instanceof CauseHolder && ((CauseHolder) result).cause instanceof CancellationException;
}
private static boolean isDone0(Object result) {
return result != null && result != UNCANCELLABLE;
}
private static final class CauseHolder {
final Throwable cause;
CauseHolder(Throwable cause) {
this.cause = cause;
}
}
static void safeExecute(EventExecutor executor, Runnable task) {
try {
executor.execute(task);
} catch (Throwable t) {
rejectedExecutionLogger.error("Failed to submit a listener notification task. Event loop shut down?", t);
}
}
@Override
public FutureCompletionStage asStage() {
return this;
}
//
private enum Marker {
EMPTY,
ERROR
}
// Just a marker
private static final Executor SAME_AS_FUTURE = task -> {
throw new UnsupportedOperationException("Marker executor. Should never be called!");
};
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return cancel();
}
@Override
public Future future() {
return this;
}
@Override
public FutureCompletionStage thenApply(Function super V, ? extends U> fn) {
return thenApplyAsync(fn, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage thenApplyAsync(Function super V, ? extends U> fn) {
return thenApplyAsync(fn, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage thenAccept(Consumer super V> action) {
return thenAcceptAsync(action, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage thenAcceptAsync(Consumer super V> action) {
return thenAcceptAsync(action, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage thenRun(Runnable action) {
return thenRunAsync(action, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage thenRunAsync(Runnable action) {
return thenRunAsync(action, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage thenCombine(
CompletionStage extends U> other, BiFunction super V, ? super U, ? extends V1> fn) {
return thenCombineAsync(other, fn, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage thenCombineAsync(
CompletionStage extends U> other, BiFunction super V, ? super U, ? extends V1> fn) {
return thenCombineAsync(other, fn, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage thenAcceptBoth(
CompletionStage extends U> other, BiConsumer super V, ? super U> action) {
return thenAcceptBothAsync(other, action, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage thenAcceptBothAsync(
CompletionStage extends U> other, BiConsumer super V, ? super U> action) {
return thenAcceptBothAsync(other, action, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage runAfterBoth(CompletionStage> other, Runnable action) {
return runAfterBothAsync(other, action, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage runAfterBothAsync(CompletionStage> other, Runnable action) {
return runAfterBothAsync(other, action, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage applyToEither(
CompletionStage extends V> other, Function super V, U> fn) {
return applyToEitherAsync(other, fn, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage applyToEitherAsync(
CompletionStage extends V> other, Function super V, U> fn) {
return applyToEitherAsync(other, fn, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage acceptEither(CompletionStage extends V> other, Consumer super V> action) {
return acceptEitherAsync(other, action, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage acceptEitherAsync(
CompletionStage extends V> other, Consumer super V> action) {
return acceptEitherAsync(other, action, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage runAfterEither(CompletionStage> other, Runnable action) {
return runAfterEitherAsync(other, action, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage runAfterEitherAsync(CompletionStage> other, Runnable action) {
return runAfterEitherAsync(other, action, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage thenCompose(Function super V, ? extends CompletionStage> fn) {
return thenComposeAsync(fn, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage thenComposeAsync(Function super V, ? extends CompletionStage> fn) {
return thenComposeAsync(fn, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage whenComplete(BiConsumer super V, ? super Throwable> action) {
return whenCompleteAsync(action, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage whenCompleteAsync(BiConsumer super V, ? super Throwable> action) {
return whenCompleteAsync(action, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage handle(BiFunction super V, Throwable, ? extends U> fn) {
return handleAsync(fn, SAME_AS_FUTURE);
}
@Override
public FutureCompletionStage handleAsync(BiFunction super V, Throwable, ? extends U> fn) {
return handleAsync(fn, ForkJoinPool.commonPool());
}
@Override
public FutureCompletionStage thenApplyAsync(Function super V, ? extends U> fn, Executor executor) {
requireNonNull(fn, "fn");
requireNonNull(executor, "executor");
Promise promise = executor().newPromise();
addListener(future -> {
Throwable cause = future.cause();
if (cause == null) {
V value = future.getNow();
if (executeDirectly(executor)) {
thenApplyAsync0(promise, value, fn);
} else {
safeExecute(executor, () -> thenApplyAsync0(promise, value, fn), promise);
}
} else {
promise.setFailure(cause);
}
});
return promise.asFuture().asStage();
}
private static void thenApplyAsync0(Promise promise, V value, Function super V, ? extends U> fn) {
final U result;
try {
result = fn.apply(value);
} catch (Throwable cause) {
promise.setFailure(cause);
return;
}
promise.setSuccess(result);
}
@Override
public FutureCompletionStage thenAcceptAsync(Consumer super V> action, Executor executor) {
requireNonNull(action, "action");
requireNonNull(executor, "executor");
Promise promise = executor().newPromise();
addListener(future -> {
Throwable cause = future.cause();
if (cause == null) {
V value = future.getNow();
if (executeDirectly(executor)) {
thenAcceptAsync0(promise, value, action);
} else {
safeExecute(executor, () -> thenAcceptAsync0(promise, value, action), promise);
}
} else {
promise.setFailure(cause);
}
});
return promise.asFuture().asStage();
}
private static void thenAcceptAsync0(Promise promise, V value, Consumer super V> action) {
try {
action.accept(value);
promise.setSuccess(null);
} catch (Throwable cause) {
promise.setFailure(cause);
}
}
@Override
public FutureCompletionStage thenRunAsync(Runnable action, Executor executor) {
return thenAcceptAsync(ignore -> action.run(), executor);
}
@Override
public FutureCompletionStage thenCombineAsync(
CompletionStage extends U> other, BiFunction super V, ? super U, ? extends V1> fn, Executor executor) {
requireNonNull(other, "other");
requireNonNull(fn, "fn");
requireNonNull(executor, "executor");
Promise promise = executor().newPromise();
AtomicReference
private static final class StacklessCancellationException extends CancellationException {
private static final long serialVersionUID = -2974906711413716191L;
// Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the
// Classloader.
@Override
public Throwable fillInStackTrace() {
return this;
}
static StacklessCancellationException newInstance(Class> clazz, String method) {
return ThrowableUtil.unknownStackTrace(new StacklessCancellationException(), clazz, method);
}
}
}