package com.lambdaworks.redis.resource; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import com.lambdaworks.redis.internal.LettuceAssert; import io.netty.util.concurrent.*; /** * Utility class to support netty's future handling. * * @author Mark Paluch * @since 3.4 */ class Futures { /** * Create a promise that emits a {@code Boolean} value on completion of the {@code future} * * @param future the future. * @return Promise emitting a {@code Boolean} value. {@literal true} if the {@code future} completed successfully, otherwise * the cause wil be transported. */ static Promise<Boolean> toBooleanPromise(Future<?> future) { final DefaultPromise<Boolean> result = new DefaultPromise<Boolean>(GlobalEventExecutor.INSTANCE); future.addListener(new GenericFutureListener<Future<Object>>() { @Override public void operationComplete(Future<Object> future) throws Exception { if (future.isSuccess()) { result.setSuccess(true); } else { result.setFailure(future.cause()); } } }); return result; } /** * Promise aggregator that aggregates multiple promises into one {@link Promise}. The aggregator workflow is: * <ol> * <li>Create a new instance of {@link com.lambdaworks.redis.resource.Futures.PromiseAggregator}</li> * <li>Call {@link #expectMore(int)} until the number of expected futures is reached</li> * <li>Arm the aggregator using {@link #arm()}</li> * <li>Add the number of futures using {@link #add(Promise[])} until the expectation is met. The added futures can be either * done or in progress.</li> * <li>The {@code aggregatePromise} is released/finished as soon as the last future/promise completes</li> * * </ol> * * @param <V> Result value type * @param <F> Future type */ static class PromiseAggregator<V, F extends Future<V>> implements GenericFutureListener<F> { private final Promise<?> aggregatePromise; private Set<Promise<V>> pendingPromises; private AtomicInteger expectedPromises = new AtomicInteger(); private AtomicInteger processedPromises = new AtomicInteger(); private boolean armed; /** * Creates a new instance. * * @param aggregatePromise the {@link Promise} to notify */ public PromiseAggregator(Promise<V> aggregatePromise) { LettuceAssert.notNull(aggregatePromise, "AggregatePromise must not be null"); this.aggregatePromise = aggregatePromise; } /** * Add the number of {@code count} to the count of expected promises. * * @param count number of futures/promises, that is added to the overall expectation count. * @throws IllegalStateException if the aggregator was armed */ public void expectMore(int count) { LettuceAssert.assertState(!armed, "Aggregator is armed and does not allow any further expectations"); expectedPromises.addAndGet(count); } /** * Arm the aggregator to expect completion of the futures. * * @throws IllegalStateException if the aggregator was armed */ public void arm() { LettuceAssert.assertState(!armed, "Aggregator is already armed"); armed = true; } /** * Add the given {@link Promise}s to the aggregator. * * @param promises the promises * @throws IllegalStateException if the aggregator was not armed */ @SafeVarargs public final PromiseAggregator<V, F> add(Promise<V>... promises) { LettuceAssert.notNull(promises, "Promises must not be null"); LettuceAssert.assertState(armed, "Aggregator is not armed and does not allow adding promises in that state. Call arm() first."); if (promises.length == 0) { return this; } synchronized (this) { if (pendingPromises == null) { int size; if (promises.length > 1) { size = promises.length; } else { size = 2; } pendingPromises = new LinkedHashSet<>(size); } for (Promise<V> p : promises) { if (p == null) { continue; } pendingPromises.add(p); p.addListener(this); } } return this; } @Override public synchronized void operationComplete(F future) throws Exception { if (pendingPromises == null) { aggregatePromise.setSuccess(null); } else { pendingPromises.remove(future); processedPromises.incrementAndGet(); if (!future.isSuccess()) { Throwable cause = future.cause(); aggregatePromise.setFailure(cause); for (Promise<V> pendingFuture : pendingPromises) { pendingFuture.setFailure(cause); } } else if (processedPromises.get() == expectedPromises.get()) { if (pendingPromises.isEmpty()) { aggregatePromise.setSuccess(null); } else { throw new IllegalStateException( "Processed promises == expected promises but pending promises is not empty. This should not have happened!"); } } } } } }