
io.netty5.util.concurrent.Futures Maven / Gradle / Ivy
/*
* Copyright 2021 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.logging.InternalLogger;
import io.netty5.util.internal.logging.InternalLoggerFactory;
import java.util.concurrent.Callable;
import java.util.function.Function;
import static io.netty5.util.internal.PromiseNotificationUtil.tryFailure;
import static java.util.Objects.requireNonNull;
/**
* Combinator operations on {@linkplain Future futures}.
*
* Used for implementing {@link Future#map(Function)} and {@link Future#flatMap(Function)}
*
* @implNote The operations themselves are implemented as static inner classes instead of lambdas to aid debugging.
*/
final class Futures {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Futures.class);
private static final PassThrough> PASS_THROUGH = new PassThrough<>();
private static final PropagateCancel PROPAGATE_CANCEL = new PropagateCancel();
/**
* Creates a new {@link Future} that will complete with the result of the given {@link Future} mapped through the
* given mapper function.
*
* If the given future fails, then the returned future will fail as well, with the same exception. Cancellation of
* either future will cancel the other. If the mapper function throws, the returned future will fail, but the given
* future will be unaffected.
*
* @param future The future whose result will flow to the returned future, through the mapping function.
* @param mapper The function that will convert the result of the given future into the result of the returned
* future.
* @param The result type of the mapper function, and of the returned future.
* @return A new future instance that will complete with the mapped result of the given future.
*/
public static Future map(Future future, Function mapper) {
requireNonNull(future, "future");
requireNonNull(mapper, "mapper");
if (future.isFailed()) {
@SuppressWarnings("unchecked") // Cast is safe because the result type is not used in failed futures.
Future failed = (Future) future;
return failed;
}
if (future.isSuccess()) {
return future.executor().submit(new CallableMapper<>(future, mapper));
}
Promise promise = future.executor().newPromise();
future.addListener(new Mapper<>(promise, mapper));
Future mappedFuture = promise.asFuture();
mappedFuture.addListener(future, propagateCancel());
return mappedFuture;
}
/**
* Creates a new {@link Future} that will complete with the result of the given {@link Future} flat-mapped through
* the given mapper function.
*
* The "flat" in "flat-map" means the given mapper function produces a result that itself is a future-of-R, yet this
* method also returns a future-of-R, rather than a future-of-future-of-R. In other words, if the same mapper
* function was used with the {@link #map(Future, Function)} method, you would get back a {@code Future>}.
* These nested futures are "flattened" into a {@code Future} by this method.
*
* Effectively, this method behaves similar to this serial code, except asynchronously and with proper exception and
* cancellation handling:
*
{@code
* V x = future.sync().getNow();
* Future y = mapper.apply(x);
* R result = y.sync().getNow();
* }
*
* If the given future fails, then the returned future will fail as well, with the same exception. Cancellation of
* either future will cancel the other. If the mapper function throws, the returned future will fail, but the given
* future will be unaffected.
*
* @param mapper The function that will convert the result of the given future into the result of the returned
* future.
* @param The result type of the mapper function, and of the returned future.
* @return A new future instance that will complete with the mapped result of the given future.
*/
public static Future flatMap(Future future, Function> mapper) {
requireNonNull(future, "future");
requireNonNull(mapper, "mapper");
Promise promise = future.executor().newPromise();
future.addListener(new FlatMapper<>(promise, mapper));
Future mappedFuture = promise.asFuture();
if (!future.isSuccess()) {
// Propagate cancellation if future is either incomplete or failed.
// Failed means it could be cancelled, so that needs to be propagated.
mappedFuture.addListener(future, propagateCancel());
}
return mappedFuture;
}
@SuppressWarnings("unchecked")
static FutureContextListener, Object> propagateCancel() {
return (FutureContextListener, Object>) (FutureContextListener, ?>) PROPAGATE_CANCEL;
}
@SuppressWarnings("unchecked")
static FutureContextListener, Object> passThrough() {
return (FutureContextListener, Object>) (FutureContextListener, ?>) PASS_THROUGH;
}
static void propagateUncommonCompletion(Future extends A> completed, Promise recipient) {
if (completed.isCancelled()) {
// Don't check or log if cancellation propagation fails.
// Propagation goes both ways, which means at least one future will already be cancelled here.
recipient.cancel();
} else {
Throwable cause = completed.cause();
recipient.tryFailure(cause);
}
}
private Futures() {
}
private static final class PropagateCancel implements FutureContextListener, Object> {
@Override
public void operationComplete(Future