package com.lambdaworks.redis.protocol; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import com.lambdaworks.redis.RedisCommandExecutionException; import com.lambdaworks.redis.RedisCommandInterruptedException; import com.lambdaworks.redis.RedisFuture; import com.lambdaworks.redis.internal.LettuceAssert; import com.lambdaworks.redis.output.CommandOutput; import io.netty.buffer.ByteBuf; /** * An asynchronous redis command and its result. All successfully executed commands will eventually return a * {@link CommandOutput} object. * * @param <K> Key type. * @param <V> Value type. * @param <T> Command output type. * * @author Mark Paluch */ public class AsyncCommand<K, V, T> extends CompletableFuture<T> implements RedisCommand<K, V, T>, RedisFuture<T>, CompleteableCommand<T>, DecoratedCommand<K, V, T> { protected CountDownLatch latch = new CountDownLatch(1); protected RedisCommand<K, V, T> command; /** * * @param command the command, must not be {@literal null}. * */ public AsyncCommand(RedisCommand<K, V, T> command) { LettuceAssert.notNull(command, "RedisCommand must not be null"); this.command = command; } /** * Wait up to the specified time for the command output to become available. * * @param timeout Maximum time to wait for a result. * @param unit Unit of time for the timeout. * * @return true if the output became available. */ @Override public boolean await(long timeout, TimeUnit unit) { try { return latch.await(timeout, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RedisCommandInterruptedException(e); } } /** * Get the object that holds this command's output. * * @return The command output object. */ @Override public CommandOutput<K, V, T> getOutput() { return command.getOutput(); } /** * Mark this command complete and notify all waiting threads. */ @Override public void complete() { if (latch.getCount() == 1) { completeResult(); command.complete(); } latch.countDown(); } protected void completeResult() { if (command.getOutput() == null) { complete(null); } else if (command.getOutput().hasError()) { completeExceptionally(new RedisCommandExecutionException(command.getOutput().getError())); } else { complete(command.getOutput().get()); } } @Override public boolean completeExceptionally(Throwable ex) { boolean result = false; if (latch.getCount() == 1) { command.completeExceptionally(ex); result = super.completeExceptionally(ex); } latch.countDown(); return result; } @Override public boolean cancel(boolean mayInterruptIfRunning) { try { command.cancel(); return super.cancel(mayInterruptIfRunning); } finally { latch.countDown(); } } @Override public String getError() { return command.getOutput().getError(); } @Override public CommandArgs<K, V> getArgs() { return command.getArgs(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append(" [type=").append(getType()); sb.append(", output=").append(getOutput()); sb.append(", commandType=").append(command.getClass().getName()); sb.append(']'); return sb.toString(); } @Override public ProtocolKeyword getType() { return command.getType(); } @Override public void cancel() { cancel(true); } @Override public void encode(ByteBuf buf) { command.encode(buf); } @Override public void setOutput(CommandOutput<K, V, T> output) { command.setOutput(output); } @Override public void onComplete(Consumer<? super T> action) { thenAccept(action); } @Override public RedisCommand<K, V, T> getDelegate() { return command; } }