Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redisson.command.CommandBatchService Maven / Gradle / Ivy
/**
* Copyright 2018 Nikita Koksharov
*
* 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 org.redisson.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.redisson.RedissonShutdownException;
import org.redisson.api.BatchOptions;
import org.redisson.api.BatchOptions.ExecutionMode;
import org.redisson.api.BatchResult;
import org.redisson.api.RFuture;
import org.redisson.client.RedisAskException;
import org.redisson.client.RedisConnection;
import org.redisson.client.RedisLoadingException;
import org.redisson.client.RedisMovedException;
import org.redisson.client.RedisTimeoutException;
import org.redisson.client.RedisTryAgainException;
import org.redisson.client.WriteRedisConnectionException;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.BatchCommandData;
import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.MasterSlaveEntry;
import org.redisson.connection.NodeSource;
import org.redisson.connection.NodeSource.Redirect;
import org.redisson.misc.CountableListener;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.pubsub.AsyncSemaphore;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.internal.PlatformDependent;
/**
*
* @author Nikita Koksharov
*
*/
public class CommandBatchService extends CommandAsyncService {
public static class ConnectionEntry {
boolean firstCommand = true;
RFuture connectionFuture;
public RFuture getConnectionFuture() {
return connectionFuture;
}
public void setConnectionFuture(RFuture connectionFuture) {
this.connectionFuture = connectionFuture;
}
public boolean isFirstCommand() {
return firstCommand;
}
public void setFirstCommand(boolean firstCommand) {
this.firstCommand = firstCommand;
}
}
public static class Entry {
Deque> commands = new LinkedBlockingDeque>();
volatile boolean readOnlyMode = true;
public Deque> getCommands() {
return commands;
}
public void setReadOnlyMode(boolean readOnlyMode) {
this.readOnlyMode = readOnlyMode;
}
public boolean isReadOnlyMode() {
return readOnlyMode;
}
public void clearErrors() {
for (BatchCommandData commandEntry : commands) {
commandEntry.clearError();
}
}
}
private final AtomicInteger index = new AtomicInteger();
private ConcurrentMap commands = PlatformDependent.newConcurrentHashMap();
private ConcurrentMap connections = PlatformDependent.newConcurrentHashMap();
private BatchOptions options;
private Map, List> nestedServices = PlatformDependent.newConcurrentHashMap();
private AtomicBoolean executed = new AtomicBoolean();
public CommandBatchService(ConnectionManager connectionManager) {
super(connectionManager);
}
public CommandBatchService(ConnectionManager connectionManager, BatchOptions options) {
super(connectionManager);
this.options = options;
}
public BatchOptions getOptions() {
return options;
}
public void add(RFuture future, List services) {
nestedServices.put(future, services);
}
@Override
protected void async(boolean readOnlyMode, NodeSource nodeSource,
Codec codec, RedisCommand command, Object[] params, RPromise mainPromise, int attempt, boolean ignoreRedirect, RFuture connFuture) {
if (executed.get()) {
throw new IllegalStateException("Batch already has been executed!");
}
if (nodeSource.getEntry() != null) {
Entry entry = commands.get(nodeSource.getEntry());
if (entry == null) {
entry = new Entry();
Entry oldEntry = commands.putIfAbsent(nodeSource.getEntry(), entry);
if (oldEntry != null) {
entry = oldEntry;
}
}
if (!readOnlyMode) {
entry.setReadOnlyMode(false);
}
Object[] batchParams = null;
if (!isRedisBasedQueue()) {
batchParams = params;
}
BatchCommandData commandData = new BatchCommandData(mainPromise, codec, command, batchParams, index.incrementAndGet());
entry.getCommands().add(commandData);
}
if (!isRedisBasedQueue()) {
return;
}
if (!readOnlyMode && this.options.getExecutionMode() == ExecutionMode.REDIS_READ_ATOMIC) {
throw new IllegalStateException("Data modification commands can't be used with queueStore=REDIS_READ_ATOMIC");
}
super.async(readOnlyMode, nodeSource, codec, command, params, mainPromise, attempt, true, connFuture);
}
AsyncSemaphore semaphore = new AsyncSemaphore(0);
@Override
protected RPromise createPromise() {
if (isRedisBasedQueue()) {
return new BatchPromise(executed);
}
return super.createPromise();
}
@Override
protected void releaseConnection(NodeSource source, RFuture connectionFuture,
boolean isReadOnly, RPromise attemptPromise, AsyncDetails details) {
if (!isRedisBasedQueue() || RedisCommands.EXEC.getName().equals(details.getCommand().getName())) {
super.releaseConnection(source, connectionFuture, isReadOnly, attemptPromise, details);
}
}
@Override
protected void handleSuccess(final AsyncDetails details, RPromise promise, RedisCommand command, R res) {
if (RedisCommands.EXEC.getName().equals(command.getName())) {
super.handleSuccess(details, promise, command, res);
return;
}
if (RedisCommands.DISCARD.getName().equals(command.getName())) {
super.handleSuccess(details, promise, command, null);
if (executed.compareAndSet(false, true)) {
details.getConnectionFuture().getNow().forceFastReconnectAsync().addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
CommandBatchService.super.releaseConnection(details.getSource(), details.getConnectionFuture(), details.isReadOnlyMode(), details.getAttemptPromise(), details);
}
});
}
return;
}
if (isRedisBasedQueue()) {
BatchPromise batchPromise = (BatchPromise) promise;
RPromise sentPromise = (RPromise) batchPromise.getSentPromise();
super.handleSuccess(details, sentPromise, command, null);
semaphore.release();
}
}
@Override
protected void handleError(final AsyncDetails details, RPromise promise, Throwable cause) {
if (isRedisBasedQueue() && promise instanceof BatchPromise) {
BatchPromise batchPromise = (BatchPromise) promise;
RPromise sentPromise = (RPromise) batchPromise.getSentPromise();
sentPromise.tryFailure(cause);
promise.tryFailure(cause);
if (executed.compareAndSet(false, true)) {
details.getConnectionFuture().getNow().forceFastReconnectAsync().addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
CommandBatchService.super.releaseConnection(details.getSource(), details.getConnectionFuture(), details.isReadOnlyMode(), details.getAttemptPromise(), details);
}
});
}
semaphore.release();
return;
}
super.handleError(details, promise, cause);
}
@Override
protected void sendCommand(AsyncDetails details, RedisConnection connection) {
if (!isRedisBasedQueue()) {
super.sendCommand(details, connection);
return;
}
ConnectionEntry connectionEntry = connections.get(details.getSource().getEntry());
if (details.getSource().getRedirect() == Redirect.ASK) {
List> list = new ArrayList>(2);
RPromise promise = new RedissonPromise();
list.add(new CommandData(promise, details.getCodec(), RedisCommands.ASKING, new Object[]{}));
if (connectionEntry.isFirstCommand()) {
list.add(new CommandData(promise, details.getCodec(), RedisCommands.MULTI, new Object[]{}));
connectionEntry.setFirstCommand(false);
}
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
RPromise main = new RedissonPromise();
ChannelFuture future = connection.send(new CommandsData(main, list, true));
details.setWriteFuture(future);
} else {
if (log.isDebugEnabled()) {
log.debug("acquired connection for command {} and params {} from slot {} using node {}... {}",
details.getCommand(), Arrays.toString(details.getParams()), details.getSource(), connection.getRedisClient().getAddr(), connection);
}
if (connectionEntry.isFirstCommand()) {
List> list = new ArrayList>(2);
list.add(new CommandData(new RedissonPromise(), details.getCodec(), RedisCommands.MULTI, new Object[]{}));
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
RPromise main = new RedissonPromise();
ChannelFuture future = connection.send(new CommandsData(main, list, true));
connectionEntry.setFirstCommand(false);
details.setWriteFuture(future);
} else {
if (RedisCommands.EXEC.getName().equals(details.getCommand().getName())) {
Entry entry = commands.get(details.getSource().getEntry());
List> list = new LinkedList>();
if (options.isSkipResult()) {
list.add(new CommandData(new RedissonPromise(), details.getCodec(), RedisCommands.CLIENT_REPLY, new Object[]{ "OFF" }));
}
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
if (options.isSkipResult()) {
list.add(new CommandData(new RedissonPromise(), details.getCodec(), RedisCommands.CLIENT_REPLY, new Object[]{ "ON" }));
}
if (options.getSyncSlaves() > 0) {
BatchCommandData waitCommand = new BatchCommandData(RedisCommands.WAIT,
new Object[] { this.options.getSyncSlaves(), this.options.getSyncTimeout() }, index.incrementAndGet());
list.add(waitCommand);
entry.getCommands().add(waitCommand);
}
RPromise main = new RedissonPromise();
ChannelFuture future = connection.send(new CommandsData(main, list, new ArrayList(entry.getCommands())));
details.setWriteFuture(future);
} else {
RPromise main = new RedissonPromise();
List> list = new LinkedList>();
list.add(new CommandData(details.getAttemptPromise(), details.getCodec(), details.getCommand(), details.getParams()));
ChannelFuture future = connection.send(new CommandsData(main, list, true));
details.setWriteFuture(future);
}
}
}
}
@Override
protected RFuture getConnection(boolean readOnlyMode, NodeSource source,
RedisCommand command) {
if (!isRedisBasedQueue()) {
return super.getConnection(readOnlyMode, source, command);
}
ConnectionEntry entry = connections.get(source.getEntry());
if (entry == null) {
entry = new ConnectionEntry();
ConnectionEntry oldEntry = connections.putIfAbsent(source.getEntry(), entry);
if (oldEntry != null) {
entry = oldEntry;
}
}
if (entry.getConnectionFuture() != null) {
return entry.getConnectionFuture();
}
synchronized (this) {
if (entry.getConnectionFuture() != null) {
return entry.getConnectionFuture();
}
RFuture connectionFuture;
if (this.options.getExecutionMode() == ExecutionMode.REDIS_WRITE_ATOMIC) {
connectionFuture = connectionManager.connectionWriteOp(source, null);
} else {
connectionFuture = connectionManager.connectionReadOp(source, null);
}
connectionFuture.syncUninterruptibly();
entry.setConnectionFuture(connectionFuture);
return connectionFuture;
}
}
public BatchResult execute() {
RFuture> f = executeAsync(BatchOptions.defaults());
return get(f);
}
public BatchResult execute(BatchOptions options) {
RFuture> f = executeAsync(options);
return get(f);
}
public RFuture executeAsyncVoid() {
final RedissonPromise promise = new RedissonPromise();
RFuture> res = executeAsync(BatchOptions.defaults());
res.addListener(new FutureListener>() {
@Override
public void operationComplete(Future> future) throws Exception {
if (future.isSuccess()) {
promise.trySuccess(null);
} else {
promise.tryFailure(future.cause());
}
}
});
return promise;
}
public RFuture> executeAsync() {
return executeAsync(BatchOptions.defaults());
}
public RFuture executeAsync(BatchOptions options) {
if (executed.get()) {
throw new IllegalStateException("Batch already executed!");
}
if (commands.isEmpty()) {
executed.set(true);
BatchResult result = new BatchResult(Collections.emptyList(), 0);
return (RFuture) RedissonPromise.newSucceededFuture(result);
}
if (this.options == null) {
this.options = options;
}
if (isRedisBasedQueue()) {
int permits = 0;
for (Entry entry : commands.values()) {
permits += entry.getCommands().size();
};
final RPromise resultPromise = new RedissonPromise();
semaphore.acquire(new Runnable() {
@Override
public void run() {
for (Entry entry : commands.values()) {
for (BatchCommandData command : entry.getCommands()) {
if (command.getPromise().isDone() && !command.getPromise().isSuccess()) {
resultPromise.tryFailure(command.getPromise().cause());
break;
}
}
}
if (resultPromise.isDone()) {
return;
}
final RPromise>> mainPromise = new RedissonPromise>>();
final Map> result = new ConcurrentHashMap>();
final CountableListener>> listener = new CountableListener>>(mainPromise, result);
listener.setCounter(connections.size());
for (final Map.Entry entry : commands.entrySet()) {
ConnectionEntry connection = connections.get(entry.getKey());
final RPromise> execPromise = new RedissonPromise>();
async(false, new NodeSource(entry.getKey()), connectionManager.getCodec(), RedisCommands.EXEC,
new Object[] {}, execPromise, 0, false, connection.getConnectionFuture());
execPromise.addListener(new FutureListener>() {
@Override
public void operationComplete(Future> future) throws Exception {
if (!future.isSuccess()) {
mainPromise.tryFailure(future.cause());
return;
}
BatchCommandData lastCommand = (BatchCommandData) entry.getValue().getCommands().peekLast();
result.put(entry.getKey(), future.getNow());
if (RedisCommands.WAIT.getName().equals(lastCommand.getCommand().getName())) {
lastCommand.getPromise().addListener(new FutureListener() {
@Override
public void operationComplete(Future ft) throws Exception {
if (!ft.isSuccess()) {
mainPromise.tryFailure(ft.cause());
return;
}
execPromise.addListener(listener);
}
});
} else {
execPromise.addListener(listener);
}
}
});
}
executed.set(true);
mainPromise.addListener(new FutureListener>>() {
@Override
public void operationComplete(Future>> future) throws Exception {
if (!future.isSuccess()) {
resultPromise.tryFailure(future.cause());
return;
}
try {
for (java.util.Map.Entry> entry : future.getNow().entrySet()) {
Entry commandEntry = commands.get(entry.getKey());
Iterator resultIter = entry.getValue().iterator();
for (BatchCommandData data : commandEntry.getCommands()) {
if (data.getCommand().getName().equals(RedisCommands.EXEC.getName())) {
break;
}
RPromise promise = (RPromise) data.getPromise();
promise.trySuccess(resultIter.next());
}
}
List entries = new ArrayList();
for (Entry e : commands.values()) {
entries.addAll(e.getCommands());
}
Collections.sort(entries);
List responses = new ArrayList(entries.size());
int syncedSlaves = 0;
for (BatchCommandData commandEntry : entries) {
if (isWaitCommand(commandEntry)) {
syncedSlaves += (Integer) commandEntry.getPromise().getNow();
} else if (!commandEntry.getCommand().getName().equals(RedisCommands.MULTI.getName())
&& !commandEntry.getCommand().getName().equals(RedisCommands.EXEC.getName())) {
Object entryResult = commandEntry.getPromise().getNow();
entryResult = tryHandleReference(entryResult);
responses.add(entryResult);
}
}
BatchResult result = new BatchResult(responses, syncedSlaves);
resultPromise.trySuccess((R)result);
} catch (Exception e) {
resultPromise.tryFailure(e);
}
commands = null;
}
});
}
}, permits);
return resultPromise;
}
executed.set(true);
if (this.options.getExecutionMode() != ExecutionMode.IN_MEMORY) {
for (Entry entry : commands.values()) {
BatchCommandData multiCommand = new BatchCommandData(RedisCommands.MULTI, new Object[] {}, index.incrementAndGet());
entry.getCommands().addFirst(multiCommand);
BatchCommandData execCommand = new BatchCommandData(RedisCommands.EXEC, new Object[] {}, index.incrementAndGet());
entry.getCommands().add(execCommand);
}
}
if (this.options.isSkipResult()) {
for (Entry entry : commands.values()) {
BatchCommandData offCommand = new BatchCommandData(RedisCommands.CLIENT_REPLY, new Object[] { "OFF" }, index.incrementAndGet());
entry.getCommands().addFirst(offCommand);
BatchCommandData onCommand = new BatchCommandData(RedisCommands.CLIENT_REPLY, new Object[] { "ON" }, index.incrementAndGet());
entry.getCommands().add(onCommand);
}
}
if (this.options.getSyncSlaves() > 0) {
for (Entry entry : commands.values()) {
BatchCommandData waitCommand = new BatchCommandData(RedisCommands.WAIT,
new Object[] { this.options.getSyncSlaves(), this.options.getSyncTimeout() }, index.incrementAndGet());
entry.getCommands().add(waitCommand);
}
}
RPromise resultPromise;
final RPromise voidPromise = new RedissonPromise();
if (this.options.isSkipResult()) {
voidPromise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
// commands = null;
nestedServices.clear();
}
});
resultPromise = (RPromise) voidPromise;
} else {
final RPromise promise = new RedissonPromise();
voidPromise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
promise.tryFailure(future.cause());
commands = null;
nestedServices.clear();
return;
}
List entries = new ArrayList();
for (Entry e : commands.values()) {
entries.addAll(e.getCommands());
}
Collections.sort(entries);
List responses = new ArrayList(entries.size());
int syncedSlaves = 0;
for (BatchCommandData commandEntry : entries) {
if (isWaitCommand(commandEntry)) {
syncedSlaves = (Integer) commandEntry.getPromise().getNow();
} else if (!commandEntry.getCommand().getName().equals(RedisCommands.MULTI.getName())
&& !commandEntry.getCommand().getName().equals(RedisCommands.EXEC.getName())) {
Object entryResult = commandEntry.getPromise().getNow();
entryResult = tryHandleReference(entryResult);
responses.add(entryResult);
}
}
BatchResult result = new BatchResult(responses, syncedSlaves);
promise.trySuccess(result);
commands = null;
nestedServices.clear();
}
});
resultPromise = (RPromise) promise;
}
final AtomicInteger slots = new AtomicInteger(commands.size());
for (java.util.Map.Entry, List> entry : nestedServices.entrySet()) {
slots.incrementAndGet();
for (CommandBatchService service : entry.getValue()) {
service.executeAsync();
}
entry.getKey().addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
handle(voidPromise, slots, future);
}
});
}
for (java.util.Map.Entry e : commands.entrySet()) {
execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, this.options);
}
return resultPromise;
}
protected boolean isRedisBasedQueue() {
return options != null && (this.options.getExecutionMode() == ExecutionMode.REDIS_READ_ATOMIC || this.options.getExecutionMode() == ExecutionMode.REDIS_WRITE_ATOMIC);
}
private void execute(final Entry entry, final NodeSource source, final RPromise mainPromise, final AtomicInteger slots,
final int attempt, final BatchOptions options) {
if (mainPromise.isCancelled()) {
free(entry);
return;
}
if (!connectionManager.getShutdownLatch().acquire()) {
free(entry);
mainPromise.tryFailure(new RedissonShutdownException("Redisson is shutdown"));
return;
}
final RPromise attemptPromise = new RedissonPromise();
final AsyncDetails details = new AsyncDetails();
final RFuture connectionFuture;
if (entry.isReadOnlyMode()) {
connectionFuture = connectionManager.connectionReadOp(source, null);
} else {
connectionFuture = connectionManager.connectionWriteOp(source, null);
}
final int attempts;
if (options.getRetryAttempts() > 0) {
attempts = options.getRetryAttempts();
} else {
attempts = connectionManager.getConfig().getRetryAttempts();
}
final long interval;
if (options.getRetryInterval() > 0) {
interval = options.getRetryInterval();
} else {
interval = connectionManager.getConfig().getRetryInterval();
}
final FutureListener mainPromiseListener = new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isCancelled() && connectionFuture.cancel(false)) {
log.debug("Connection obtaining canceled for batch");
details.getTimeout().cancel();
if (attemptPromise.cancel(false)) {
free(entry);
}
}
}
};
final TimerTask retryTimerTask = new TimerTask() {
@Override
public void run(Timeout t) throws Exception {
if (attemptPromise.isDone()) {
return;
}
if (connectionFuture.cancel(false)) {
if (details.getException() == null) {
details.setException(new RedisTimeoutException("Unable to get connection! "
+ "Node source: " + source + " after " + attempts + " retry attempts"));
}
connectionManager.getShutdownLatch().release();
} else {
if (connectionFuture.isSuccess()) {
if (details.getWriteFuture() == null || !details.getWriteFuture().isDone()) {
if (details.getAttempt() == attempts) {
if (details.getWriteFuture() != null && details.getWriteFuture().cancel(false)) {
if (details.getException() == null) {
details.setException(new RedisTimeoutException("Unable to send batch after " + attempts + " retry attempts"));
}
attemptPromise.tryFailure(details.getException());
}
return;
}
details.incAttempt();
Timeout timeout = connectionManager.newTimeout(this, interval, TimeUnit.MILLISECONDS);
details.setTimeout(timeout);
return;
}
if (details.getWriteFuture().isDone() && details.getWriteFuture().isSuccess()) {
return;
}
}
}
if (mainPromise.isCancelled()) {
if (attemptPromise.cancel(false)) {
free(entry);
}
return;
}
if (attempt == attempts) {
if (details.getException() == null) {
details.setException(new RedisTimeoutException("Batch command execution timeout"));
}
attemptPromise.tryFailure(details.getException());
return;
}
if (!attemptPromise.cancel(false)) {
return;
}
int count = attempt + 1;
mainPromise.removeListener(mainPromiseListener);
execute(entry, source, mainPromise, slots, count, options);
}
};
Timeout timeout = connectionManager.newTimeout(retryTimerTask, interval, TimeUnit.MILLISECONDS);
details.setTimeout(timeout);
mainPromise.addListener(mainPromiseListener);
connectionFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future connFuture) throws Exception {
checkConnectionFuture(entry, source, mainPromise, attemptPromise, details, connectionFuture, options.isSkipResult(),
options.getResponseTimeout(), attempts, options.getExecutionMode());
}
});
attemptPromise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
details.getTimeout().cancel();
if (future.isCancelled()) {
return;
}
mainPromise.removeListener(mainPromiseListener);
if (future.cause() instanceof RedisMovedException) {
RedisMovedException ex = (RedisMovedException)future.cause();
entry.clearErrors();
NodeSource nodeSource = new NodeSource(ex.getSlot(), ex.getUrl(), Redirect.MOVED);
execute(entry, nodeSource, mainPromise, slots, attempt, options);
return;
}
if (future.cause() instanceof RedisAskException) {
RedisAskException ex = (RedisAskException)future.cause();
entry.clearErrors();
NodeSource nodeSource = new NodeSource(ex.getSlot(), ex.getUrl(), Redirect.ASK);
execute(entry, nodeSource, mainPromise, slots, attempt, options);
return;
}
if (future.cause() instanceof RedisLoadingException) {
entry.clearErrors();
execute(entry, source, mainPromise, slots, attempt, options);
return;
}
if (future.cause() instanceof RedisTryAgainException) {
entry.clearErrors();
connectionManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
execute(entry, source, mainPromise, slots, attempt, options);
}
}, 1, TimeUnit.SECONDS);
return;
}
free(entry);
handle(mainPromise, slots, future);
}
});
}
protected void free(final Entry entry) {
for (BatchCommandData command : entry.getCommands()) {
free(command.getParams());
}
}
private void checkWriteFuture(Entry entry, final RPromise attemptPromise, AsyncDetails details,
final RedisConnection connection, ChannelFuture future, long responseTimeout, int attempts) {
if (future.isCancelled() || attemptPromise.isDone()) {
return;
}
if (!future.isSuccess()) {
details.setException(new WriteRedisConnectionException("Can't write command batch to channel: " + future.channel(), future.cause()));
if (details.getAttempt() == attempts) {
attemptPromise.tryFailure(details.getException());
}
return;
}
details.getTimeout().cancel();
TimerTask timerTask = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
attemptPromise.tryFailure(
new RedisTimeoutException("Redis server response timeout during command batch execution. Channel: " + connection.getChannel()));
}
};
long timeout = connectionManager.getConfig().getTimeout();
if (responseTimeout > 0) {
timeout = responseTimeout;
}
Timeout timeoutTask = connectionManager.newTimeout(timerTask, timeout, TimeUnit.MILLISECONDS);
details.setTimeout(timeoutTask);
}
private void checkConnectionFuture(final Entry entry, final NodeSource source,
final RPromise mainPromise, final RPromise attemptPromise, final AsyncDetails details,
RFuture connFuture, final boolean noResult, final long responseTimeout, final int attempts, ExecutionMode executionMode) {
if (connFuture.isCancelled()) {
return;
}
if (!connFuture.isSuccess()) {
connectionManager.getShutdownLatch().release();
details.setException(convertException(connFuture));
return;
}
if (attemptPromise.isDone() || mainPromise.isDone()) {
releaseConnection(source, connFuture, details.isReadOnlyMode(), attemptPromise, details);
return;
}
final RedisConnection connection = connFuture.getNow();
boolean isAtomic = executionMode != ExecutionMode.IN_MEMORY;
boolean isQueued = executionMode == ExecutionMode.REDIS_READ_ATOMIC || executionMode == ExecutionMode.REDIS_WRITE_ATOMIC;
List> list = new LinkedList>();
if (source.getRedirect() == Redirect.ASK) {
RPromise promise = new RedissonPromise();
list.add(new CommandData(promise, StringCodec.INSTANCE, RedisCommands.ASKING, new Object[] {}));
}
for (BatchCommandData c : entry.getCommands()) {
if (c.getPromise().isSuccess() && !isWaitCommand(c) && !isAtomic) {
// skip successful commands
continue;
}
list.add(c);
}
ChannelFuture future = connection.send(new CommandsData(attemptPromise, list, noResult, isAtomic, isQueued));
details.setWriteFuture(future);
details.getWriteFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
checkWriteFuture(entry, attemptPromise, details, connection, future, responseTimeout, attempts);
}
});
releaseConnection(source, connFuture, entry.isReadOnlyMode(), attemptPromise, details);
}
protected boolean isWaitCommand(BatchCommandData c) {
return c.getCommand().getName().equals(RedisCommands.WAIT.getName());
}
protected void handle(final RPromise mainPromise, final AtomicInteger slots, Future future) {
if (future.isSuccess()) {
if (slots.decrementAndGet() == 0) {
mainPromise.trySuccess(null);
}
} else {
mainPromise.tryFailure(future.cause());
}
}
}