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.CommandAsyncService 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.security.MessageDigest;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.redisson.RedissonReference;
import org.redisson.RedissonShutdownException;
import org.redisson.ScanResult;
import org.redisson.SlotCallback;
import org.redisson.api.RFuture;
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonReactiveClient;
import org.redisson.api.RedissonRxClient;
import org.redisson.cache.LRUCacheMap;
import org.redisson.client.RedisAskException;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisConnection;
import org.redisson.client.RedisException;
import org.redisson.client.RedisLoadingException;
import org.redisson.client.RedisMovedException;
import org.redisson.client.RedisRedirectException;
import org.redisson.client.RedisResponseTimeoutException;
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.CommandData;
import org.redisson.client.protocol.CommandsData;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.ScoredEntry;
import org.redisson.client.protocol.decoder.ListScanResult;
import org.redisson.client.protocol.decoder.MapScanResult;
import org.redisson.codec.ReferenceCodecProvider;
import org.redisson.config.Config;
import org.redisson.config.MasterSlaveServersConfig;
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.LogHelper;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonObjectFactory;
import org.redisson.misc.RedissonPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
*/
public class CommandAsyncService implements CommandAsyncExecutor {
static final Logger log = LoggerFactory.getLogger(CommandAsyncService.class);
final ConnectionManager connectionManager;
protected RedissonClient redisson;
protected RedissonReactiveClient redissonReactive;
protected RedissonRxClient redissonRx;
public CommandAsyncService(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
@Override
public ConnectionManager getConnectionManager() {
return connectionManager;
}
@Override
public CommandAsyncExecutor enableRedissonReferenceSupport(RedissonClient redisson) {
if (redisson != null) {
this.redisson = redisson;
enableRedissonReferenceSupport(redisson.getConfig());
this.redissonReactive = null;
this.redissonRx = null;
}
return this;
}
@Override
public CommandAsyncExecutor enableRedissonReferenceSupport(RedissonReactiveClient redissonReactive) {
if (redissonReactive != null) {
this.redissonReactive = redissonReactive;
enableRedissonReferenceSupport(redissonReactive.getConfig());
this.redisson = null;
this.redissonRx = null;
}
return this;
}
@Override
public CommandAsyncExecutor enableRedissonReferenceSupport(RedissonRxClient redissonRx) {
if (redissonRx != null) {
this.redissonReactive = null;
enableRedissonReferenceSupport(redissonRx.getConfig());
this.redisson = null;
this.redissonRx = redissonRx;
}
return this;
}
private void enableRedissonReferenceSupport(Config config) {
Codec codec = config.getCodec();
ReferenceCodecProvider codecProvider = config.getReferenceCodecProvider();
codecProvider.registerCodec((Class) codec.getClass(), codec);
}
@Override
public boolean isRedissonReferenceSupportEnabled() {
return redisson != null || redissonReactive != null || redissonRx != null;
}
@Override
public void syncSubscription(RFuture> future) {
MasterSlaveServersConfig config = connectionManager.getConfig();
try {
int timeout = config.getTimeout() + config.getRetryInterval() * config.getRetryAttempts();
if (!future.await(timeout)) {
((RPromise)future).tryFailure(new RedisTimeoutException("Subscribe timeout: (" + timeout + "ms)"));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
future.syncUninterruptibly();
}
@Override
public V get(RFuture future) {
if (!future.isDone()) {
final CountDownLatch l = new CountDownLatch(1);
future.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
l.countDown();
}
});
boolean interrupted = false;
while (!future.isDone()) {
try {
l.await();
} catch (InterruptedException e) {
interrupted = true;
break;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
}
// commented out due to blocking issues up to 200 ms per minute for each thread
// future.awaitUninterruptibly();
if (future.isSuccess()) {
return future.getNow();
}
throw convertException(future);
}
@Override
public boolean await(RFuture> future, long timeout, TimeUnit timeoutUnit) throws InterruptedException {
final CountDownLatch l = new CountDownLatch(1);
future.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
l.countDown();
}
});
return l.await(timeout, timeoutUnit);
}
protected RPromise createPromise() {
return new RedissonPromise();
}
@Override
public RFuture readAsync(RedisClient client, MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
async(true, new NodeSource(entry, client), codec, command, params, mainPromise, 0, false);
return mainPromise;
}
@Override
public RFuture readAsync(RedisClient client, String name, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
int slot = connectionManager.calcSlot(name);
async(true, new NodeSource(slot, client), codec, command, params, mainPromise, 0, false);
return mainPromise;
}
public RFuture readAsync(RedisClient client, byte[] key, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
int slot = connectionManager.calcSlot(key);
async(true, new NodeSource(slot, client), codec, command, params, mainPromise, 0, false);
return mainPromise;
}
@Override
public RFuture readAsync(RedisClient client, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
async(true, new NodeSource(client), codec, command, params, mainPromise, 0, false);
return mainPromise;
}
@Override
public RFuture> readAllAsync(Codec codec, RedisCommand command, Object... params) {
List results = new ArrayList();
return readAllAsync(results, codec, command, params);
}
@Override
public RFuture> readAllAsync(RedisCommand command, Object... params) {
List results = new ArrayList();
return readAllAsync(results, connectionManager.getCodec(), command, params);
}
@Override
public RFuture> readAllAsync(final Collection results, Codec codec, RedisCommand command, Object... params) {
final RPromise> mainPromise = createPromise();
final Collection nodes = connectionManager.getEntrySet();
final AtomicInteger counter = new AtomicInteger(nodes.size());
FutureListener listener = new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess() && !(future.cause() instanceof RedisRedirectException)) {
mainPromise.tryFailure(future.cause());
return;
}
Object result = future.getNow();
if (result instanceof Collection) {
synchronized (results) {
results.addAll((Collection) result);
}
} else {
synchronized (results) {
results.add((R) result);
}
}
if (counter.decrementAndGet() == 0
&& !mainPromise.isDone()) {
mainPromise.trySuccess(results);
}
}
};
for (MasterSlaveEntry entry : nodes) {
RPromise promise = new RedissonPromise();
promise.addListener(listener);
async(true, new NodeSource(entry), codec, command, params, promise, 0, true);
}
return mainPromise;
}
@Override
public RFuture readRandomAsync(Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
List nodes = new ArrayList(connectionManager.getEntrySet());
Collections.shuffle(nodes);
retryReadRandomAsync(codec, command, mainPromise, nodes, params);
return mainPromise;
}
@Override
public RFuture readRandomAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
retryReadRandomAsync(codec, command, mainPromise, Collections.singletonList(entry), params);
return mainPromise;
}
private void retryReadRandomAsync(final Codec codec, final RedisCommand command, final RPromise mainPromise,
final List nodes, final Object... params) {
final RPromise attemptPromise = new RedissonPromise();
attemptPromise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isSuccess()) {
if (future.getNow() == null) {
if (nodes.isEmpty()) {
mainPromise.trySuccess(null);
} else {
retryReadRandomAsync(codec, command, mainPromise, nodes, params);
}
} else {
mainPromise.trySuccess(future.getNow());
}
} else {
mainPromise.tryFailure(future.cause());
}
}
});
MasterSlaveEntry entry = nodes.remove(0);
async(true, new NodeSource(entry), codec, command, params, attemptPromise, 0, false);
}
@Override
public RFuture writeAllAsync(RedisCommand command, Object... params) {
return writeAllAsync(command, null, params);
}
@Override
public RFuture writeAllAsync(RedisCommand command, SlotCallback callback, Object... params) {
return allAsync(false, connectionManager.getCodec(), command, callback, params);
}
@Override
public RFuture writeAllAsync(Codec codec, RedisCommand command, SlotCallback callback, Object... params) {
return allAsync(false, codec, command, callback, params);
}
@Override
public RFuture readAllAsync(RedisCommand command, SlotCallback callback, Object... params) {
return allAsync(true, connectionManager.getCodec(), command, callback, params);
}
private RFuture allAsync(boolean readOnlyMode, Codec codec, final RedisCommand command, final SlotCallback callback, Object... params) {
final RPromise mainPromise = new RedissonPromise();
final Collection nodes = connectionManager.getEntrySet();
final AtomicInteger counter = new AtomicInteger(nodes.size());
FutureListener listener = new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess() && !(future.cause() instanceof RedisRedirectException)) {
mainPromise.tryFailure(future.cause());
return;
}
T result = future.getNow();
if (future.cause() instanceof RedisRedirectException) {
result = command.getConvertor().convert(result);
}
if (callback != null) {
callback.onSlotResult(result);
}
if (counter.decrementAndGet() == 0) {
if (callback != null) {
mainPromise.trySuccess(callback.onFinish());
} else {
mainPromise.trySuccess(null);
}
}
}
};
for (MasterSlaveEntry entry : nodes) {
RPromise promise = new RedissonPromise();
promise.addListener(listener);
async(readOnlyMode, new NodeSource(entry), codec, command, params, promise, 0, true);
}
return mainPromise;
}
public RedisException convertException(RFuture future) {
return future.cause() instanceof RedisException
? (RedisException) future.cause()
: new RedisException("Unexpected exception while processing command", future.cause());
}
private NodeSource getNodeSource(String key) {
int slot = connectionManager.calcSlot(key);
MasterSlaveEntry entry = connectionManager.getEntry(slot);
return new NodeSource(entry);
}
private NodeSource getNodeSource(byte[] key) {
int slot = connectionManager.calcSlot(key);
MasterSlaveEntry entry = connectionManager.getEntry(slot);
return new NodeSource(entry);
}
@Override
public RFuture readAsync(String key, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
NodeSource source = getNodeSource(key);
async(true, source, codec, command, params, mainPromise, 0, false);
return mainPromise;
}
@Override
public RFuture readAsync(byte[] key, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
NodeSource source = getNodeSource(key);
async(true, source, codec, command, params, mainPromise, 0, false);
return mainPromise;
}
public RFuture readAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
async(true, new NodeSource(entry), codec, command, params, mainPromise, 0, false);
return mainPromise;
}
@Override
public RFuture writeAsync(MasterSlaveEntry entry, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
async(false, new NodeSource(entry), codec, command, params, mainPromise, 0, false);
return mainPromise;
}
@Override
public RFuture readAsync(String key, RedisCommand command, Object... params) {
return readAsync(key, connectionManager.getCodec(), command, params);
}
@Override
public RFuture evalReadAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
NodeSource source = getNodeSource(key);
return evalAsync(source, true, codec, evalCommandType, script, keys, params);
}
@Override
public RFuture evalReadAsync(MasterSlaveEntry entry, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
return evalAsync(new NodeSource(entry), true, codec, evalCommandType, script, keys, params);
}
@Override
public RFuture evalReadAsync(RedisClient client, String name, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
int slot = connectionManager.calcSlot(name);
return evalAsync(new NodeSource(slot, client), true, codec, evalCommandType, script, keys, params);
}
@Override
public RFuture evalWriteAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
NodeSource source = getNodeSource(key);
return evalAsync(source, false, codec, evalCommandType, script, keys, params);
}
public RFuture evalWriteAsync(MasterSlaveEntry entry, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) {
return evalAsync(new NodeSource(entry), false, codec, evalCommandType, script, keys, params);
}
@Override
public RFuture evalWriteAllAsync(RedisCommand command, SlotCallback callback, String script, List keys, Object... params) {
return evalAllAsync(false, command, callback, script, keys, params);
}
public RFuture evalAllAsync(boolean readOnlyMode, RedisCommand command, final SlotCallback callback, String script, List keys, Object... params) {
final RPromise mainPromise = new RedissonPromise();
final Collection entries = connectionManager.getEntrySet();
final AtomicInteger counter = new AtomicInteger(entries.size());
FutureListener listener = new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess() && !(future.cause() instanceof RedisRedirectException)) {
mainPromise.tryFailure(future.cause());
return;
}
callback.onSlotResult(future.getNow());
if (counter.decrementAndGet() == 0
&& !mainPromise.isDone()) {
mainPromise.trySuccess(callback.onFinish());
}
}
};
List args = new ArrayList(2 + keys.size() + params.length);
args.add(script);
args.add(keys.size());
args.addAll(keys);
args.addAll(Arrays.asList(params));
for (MasterSlaveEntry entry : entries) {
RPromise promise = new RedissonPromise();
promise.addListener(listener);
async(readOnlyMode, new NodeSource(entry), connectionManager.getCodec(), command, args.toArray(), promise, 0, true);
}
return mainPromise;
}
private RFuture loadScript(List keys, String script) {
if (!keys.isEmpty()) {
Object key = keys.get(0);
if (key instanceof byte[]) {
return writeAsync((byte[])key, StringCodec.INSTANCE, RedisCommands.SCRIPT_LOAD, script);
}
return writeAsync((String)key, StringCodec.INSTANCE, RedisCommands.SCRIPT_LOAD, script);
}
return writeAllAsync(RedisCommands.SCRIPT_LOAD, new SlotCallback() {
volatile String result;
@Override
public void onSlotResult(String result) {
this.result = result;
}
@Override
public String onFinish() {
return result;
}
}, script);
}
protected boolean isEvalCacheActive() {
return getConnectionManager().getCfg().isUseScriptCache();
}
private static final Map shaCache = new LRUCacheMap(500, 0, 0);
private String calcSHA(String script) {
String digest = shaCache.get(script);
if (digest == null) {
try {
MessageDigest mdigest = MessageDigest.getInstance("SHA-1");
byte[] s = mdigest.digest(script.getBytes());
digest = ByteBufUtil.hexDump(s);
shaCache.put(script, digest);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
return digest;
}
private Object[] copy(Object[] params) {
List result = new ArrayList();
for (Object object : params) {
if (object instanceof ByteBuf) {
ByteBuf b = ((ByteBuf) object);
ByteBuf nb = ByteBufAllocator.DEFAULT.buffer(b.readableBytes());
int ri = b.readerIndex();
nb.writeBytes(b);
b.readerIndex(ri);
result.add(nb);
} else {
result.add(object);
}
}
return result.toArray();
}
private RFuture evalAsync(final NodeSource nodeSource, boolean readOnlyMode, final Codec codec, final RedisCommand evalCommandType, final String script, final List keys, final Object... params) {
if (isEvalCacheActive() && evalCommandType.getName().equals("EVAL")) {
final RPromise mainPromise = new RedissonPromise();
final Object[] pps = copy(params);
RPromise promise = new RedissonPromise();
final String sha1 = calcSHA(script);
RedisCommand command = new RedisCommand(evalCommandType, "EVALSHA");
List args = new ArrayList(2 + keys.size() + params.length);
args.add(sha1);
args.add(keys.size());
args.addAll(keys);
args.addAll(Arrays.asList(params));
async(false, nodeSource, codec, command, args.toArray(), promise, 0, false);
promise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
if (future.cause().getMessage().startsWith("NOSCRIPT")) {
RFuture loadFuture = loadScript(keys, script);
loadFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
free(pps);
mainPromise.tryFailure(future.cause());
return;
}
RedisCommand command = new RedisCommand(evalCommandType, "EVALSHA");
List args = new ArrayList(2 + keys.size() + params.length);
args.add(sha1);
args.add(keys.size());
args.addAll(keys);
args.addAll(Arrays.asList(pps));
async(false, nodeSource, codec, command, args.toArray(), mainPromise, 0, false);
}
});
} else {
free(pps);
mainPromise.tryFailure(future.cause());
}
return;
}
free(pps);
mainPromise.trySuccess(future.getNow());
}
});
return mainPromise;
}
RPromise mainPromise = createPromise();
List args = new ArrayList(2 + keys.size() + params.length);
args.add(script);
args.add(keys.size());
args.addAll(keys);
args.addAll(Arrays.asList(params));
async(readOnlyMode, nodeSource, codec, evalCommandType, args.toArray(), mainPromise, 0, false);
return mainPromise;
}
@Override
public RFuture writeAsync(String key, RedisCommand command, Object... params) {
return writeAsync(key, connectionManager.getCodec(), command, params);
}
@Override
public RFuture writeAsync(String key, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
NodeSource source = getNodeSource(key);
async(false, source, codec, command, params, mainPromise, 0, false);
return mainPromise;
}
public RFuture writeAsync(byte[] key, Codec codec, RedisCommand command, Object... params) {
RPromise mainPromise = createPromise();
NodeSource source = getNodeSource(key);
async(false, source, codec, command, params, mainPromise, 0, false);
return mainPromise;
}
public void async(final boolean readOnlyMode, final NodeSource source, final Codec codec,
final RedisCommand command, final Object[] params, final RPromise mainPromise, final int attempt,
final boolean ignoreRedirect) {
if (mainPromise.isCancelled()) {
free(params);
return;
}
if (!connectionManager.getShutdownLatch().acquire()) {
free(params);
mainPromise.tryFailure(new RedissonShutdownException("Redisson is shutdown"));
return;
}
final AsyncDetails details = AsyncDetails.acquire();
final RFuture connectionFuture = getConnection(readOnlyMode, source, command);
final RPromise attemptPromise = new RedissonPromise();
details.init(connectionFuture, attemptPromise,
readOnlyMode, source, codec, command, params, mainPromise, attempt);
FutureListener mainPromiseListener = new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isCancelled() && connectionFuture.cancel(false)) {
log.debug("Connection obtaining canceled for {}", command);
details.getTimeout().cancel();
if (details.getAttemptPromise().cancel(false)) {
free(params);
}
}
}
};
final TimerTask retryTimerTask = new TimerTask() {
@Override
public void run(Timeout t) throws Exception {
if (details.getAttemptPromise().isDone()) {
return;
}
if (details.getConnectionFuture().cancel(false)) {
if (details.getException() == null) {
details.setException(new RedisTimeoutException("Unable to get connection! "
+ "Node source: " + source
+ ", command: " + command + ", command params: " + LogHelper.toString(details.getParams())
+ " after " + details.getAttempt() + " retry attempts"));
}
connectionManager.getShutdownLatch().release();
} else {
if (details.getConnectionFuture().isSuccess()) {
if (details.getWriteFuture() == null || !details.getWriteFuture().isDone()) {
if (details.getAttempt() == connectionManager.getConfig().getRetryAttempts()) {
if (details.getWriteFuture() != null && details.getWriteFuture().cancel(false)) {
if (details.getException() == null) {
details.setException(new RedisTimeoutException("Unable to send command! "
+ "Node source: " + source + ", connection: " + details.getConnectionFuture().getNow()
+ ", command: " + command + ", command params: " + LogHelper.toString(details.getParams())
+ " after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts"));
}
details.getAttemptPromise().tryFailure(details.getException());
}
return;
}
details.incAttempt();
Timeout timeout = connectionManager.newTimeout(this, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS);
details.setTimeout(timeout);
return;
}
if (details.getWriteFuture().isDone() && details.getWriteFuture().isSuccess()) {
return;
}
}
}
if (details.getMainPromise().isCancelled()) {
if (details.getAttemptPromise().cancel(false)) {
free(details.getParams());
AsyncDetails.release(details);
}
return;
}
if (details.getAttempt() == connectionManager.getConfig().getRetryAttempts()) {
if (details.getException() == null) {
details.setException(new RedisTimeoutException("Unable to send command! Node source: " + source
+ ", command: " + command + ", command params: " + LogHelper.toString(details.getParams())
+ " after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts"));
}
details.getAttemptPromise().tryFailure(details.getException());
return;
}
if (!details.getAttemptPromise().cancel(false)) {
return;
}
int count = details.getAttempt() + 1;
if (log.isDebugEnabled()) {
log.debug("attempt {} for command {} and params {}",
count, details.getCommand(), LogHelper.toString(details.getParams()));
}
details.removeMainPromiseListener();
async(details.isReadOnlyMode(), details.getSource(), details.getCodec(), details.getCommand(), details.getParams(), details.getMainPromise(), count, ignoreRedirect);
AsyncDetails.release(details);
}
};
Timeout timeout = connectionManager.newTimeout(retryTimerTask, connectionManager.getConfig().getRetryInterval(), TimeUnit.MILLISECONDS);
details.setTimeout(timeout);
details.setupMainPromiseListener(mainPromiseListener);
connectionFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future connFuture) throws Exception {
if (connFuture.isCancelled()) {
return;
}
if (!connFuture.isSuccess()) {
connectionManager.getShutdownLatch().release();
details.setException(convertException(connectionFuture));
return;
}
if (details.getAttemptPromise().isDone() || details.getMainPromise().isDone()) {
releaseConnection(source, connectionFuture, details.isReadOnlyMode(), details.getAttemptPromise(), details);
return;
}
final RedisConnection connection = connFuture.getNow();
sendCommand(details, connection);
details.getWriteFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
checkWriteFuture(details, ignoreRedirect, connection);
}
});
releaseConnection(source, connectionFuture, details.isReadOnlyMode(), details.getAttemptPromise(), details);
}
});
attemptPromise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
checkAttemptFuture(source, details, future, ignoreRedirect);
}
});
}
protected RFuture getConnection(final boolean readOnlyMode, final NodeSource source,
final RedisCommand command) {
final RFuture connectionFuture;
if (readOnlyMode) {
connectionFuture = connectionManager.connectionReadOp(source, command);
} else {
connectionFuture = connectionManager.connectionWriteOp(source, command);
}
return connectionFuture;
}
protected void free(final Object[] params) {
for (Object obj : params) {
ReferenceCountUtil.safeRelease(obj);
}
}
private void checkWriteFuture(final AsyncDetails details, final boolean ignoreRedirect, final RedisConnection connection) {
ChannelFuture future = details.getWriteFuture();
if (future.isCancelled() || details.getAttemptPromise().isDone()) {
return;
}
if (!future.isSuccess()) {
details.setException(new WriteRedisConnectionException(
"Unable to send command! Node source: " + details.getSource() + ", connection: " + connection +
", command: " + details.getCommand() + ", command params: " + LogHelper.toString(details.getParams())
+ " after " + details.getAttempt() + " retry attempts", future.cause()));
if (details.getAttempt() == connectionManager.getConfig().getRetryAttempts()) {
if (!details.getAttemptPromise().tryFailure(details.getException())) {
log.error(details.getException().getMessage());
}
}
return;
}
details.getTimeout().cancel();
long timeoutTime = connectionManager.getConfig().getTimeout();
if (RedisCommands.BLOCKING_COMMAND_NAMES.contains(details.getCommand().getName())
|| RedisCommands.BLOCKING_COMMANDS.contains(details.getCommand())) {
Long popTimeout = null;
if (RedisCommands.BLOCKING_COMMANDS.contains(details.getCommand())) {
boolean found = false;
for (Object param : details.getParams()) {
if (found) {
popTimeout = Long.valueOf(param.toString()) / 1000;
break;
}
if (param instanceof String) {
found = true;
}
}
} else {
popTimeout = Long.valueOf(details.getParams()[details.getParams().length - 1].toString());
}
handleBlockingOperations(details, connection, popTimeout);
if (popTimeout == 0) {
return;
}
timeoutTime += popTimeout * 1000;
// add 1 second due to issue https://github.com/antirez/redis/issues/874
timeoutTime += 1000;
}
final long timeoutAmount = timeoutTime;
TimerTask timeoutTask = new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (details.getAttempt() < connectionManager.getConfig().getRetryAttempts()) {
if (!details.getAttemptPromise().cancel(false)) {
return;
}
int count = details.getAttempt() + 1;
if (log.isDebugEnabled()) {
log.debug("attempt {} for command {} and params {}",
count, details.getCommand(), LogHelper.toString(details.getParams()));
}
details.removeMainPromiseListener();
async(details.isReadOnlyMode(), details.getSource(), details.getCodec(), details.getCommand(), details.getParams(), details.getMainPromise(), count, ignoreRedirect);
AsyncDetails.release(details);
return;
}
details.getAttemptPromise().tryFailure(
new RedisResponseTimeoutException("Redis server response timeout (" + timeoutAmount + " ms) occured"
+ " after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts. Command: " + details.getCommand()
+ ", params: " + LogHelper.toString(details.getParams()) + ", channel: " + connection.getChannel()));
}
};
Timeout timeout = connectionManager.newTimeout(timeoutTask, timeoutTime, TimeUnit.MILLISECONDS);
details.setTimeout(timeout);
}
private void handleBlockingOperations(final AsyncDetails details, final RedisConnection connection, Long popTimeout) {
final FutureListener listener = new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
details.getMainPromise().tryFailure(new RedissonShutdownException("Redisson is shutdown"));
}
};
final Timeout scheduledFuture;
if (popTimeout != 0) {
// handling cases when connection has been lost
scheduledFuture = connectionManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (details.getAttemptPromise().trySuccess(null)) {
connection.forceFastReconnectAsync();
}
}
}, popTimeout, TimeUnit.SECONDS);
} else {
scheduledFuture = null;
}
details.getMainPromise().addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (scheduledFuture != null) {
scheduledFuture.cancel();
}
synchronized (listener) {
connectionManager.getShutdownPromise().removeListener(listener);
}
// handling cancel operation for blocking commands
if (future.isCancelled() && !details.getAttemptPromise().isDone()) {
log.debug("Canceled blocking operation {} used {}", details.getCommand(), connection);
connection.forceFastReconnectAsync().addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
details.getAttemptPromise().cancel(true);
}
});
return;
}
if (future.cause() instanceof RedissonShutdownException) {
details.getAttemptPromise().tryFailure(future.cause());
}
}
});
synchronized (listener) {
if (!details.getMainPromise().isDone()) {
connectionManager.getShutdownPromise().addListener(listener);
}
}
}
protected void releaseConnection(final NodeSource source, final RFuture connectionFuture,
final boolean isReadOnly, RPromise attemptPromise, final AsyncDetails details) {
attemptPromise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!connectionFuture.isSuccess()) {
return;
}
RedisConnection connection = connectionFuture.getNow();
connectionManager.getShutdownLatch().release();
if (isReadOnly) {
connectionManager.releaseRead(source, connection);
} else {
connectionManager.releaseWrite(source, connection);
}
if (log.isDebugEnabled()) {
log.debug("connection released for command {} and params {} from slot {} using connection {}",
details.getCommand(), LogHelper.toString(details.getParams()), details.getSource(), connection);
}
}
});
}
protected void checkAttemptFuture(final NodeSource source, final AsyncDetails details,
Future future, final boolean ignoreRedirect) {
details.getTimeout().cancel();
if (future.isCancelled()) {
return;
}
try {
details.removeMainPromiseListener();
if (future.cause() instanceof RedisMovedException && !ignoreRedirect) {
RedisMovedException ex = (RedisMovedException) future.cause();
if (source.getRedirect() == Redirect.MOVED) {
details.getMainPromise().tryFailure(new RedisException("MOVED redirection loop detected. Node " + source.getAddr() + " has further redirect to " + ex.getUrl()));
return;
}
async(details.isReadOnlyMode(), new NodeSource(ex.getSlot(), ex.getUrl(), Redirect.MOVED), details.getCodec(),
details.getCommand(), details.getParams(), details.getMainPromise(), details.getAttempt(), ignoreRedirect);
AsyncDetails.release(details);
return;
}
if (future.cause() instanceof RedisAskException && !ignoreRedirect) {
RedisAskException ex = (RedisAskException) future.cause();
async(details.isReadOnlyMode(), new NodeSource(ex.getSlot(), ex.getUrl(), Redirect.ASK), details.getCodec(),
details.getCommand(), details.getParams(), details.getMainPromise(), details.getAttempt(), ignoreRedirect);
AsyncDetails.release(details);
return;
}
if (future.cause() instanceof RedisLoadingException) {
async(details.isReadOnlyMode(), source, details.getCodec(),
details.getCommand(), details.getParams(), details.getMainPromise(), details.getAttempt(), ignoreRedirect);
AsyncDetails.release(details);
return;
}
if (future.cause() instanceof RedisTryAgainException) {
connectionManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
async(details.isReadOnlyMode(), source, details.getCodec(),
details.getCommand(), details.getParams(), details.getMainPromise(), details.getAttempt(), ignoreRedirect);
}
}, 1, TimeUnit.SECONDS);
AsyncDetails.release(details);
return;
}
free(details.getParams());
if (future.isSuccess()) {
R res = future.getNow();
if (res instanceof ScanResult) {
((ScanResult) res).setRedisClient(details.getConnectionFuture().getNow().getRedisClient());
}
handleSuccess(details, details.getMainPromise(), details.getCommand(), res);
} else {
handleError(details, details.getMainPromise(), future.cause());
}
AsyncDetails.release(details);
} catch (RuntimeException e) {
handleError(details, details.getMainPromise(), e);
throw e;
}
}
protected void handleError(AsyncDetails details, RPromise mainPromise, Throwable cause) {
mainPromise.tryFailure(cause);
}
protected void handleSuccess(AsyncDetails details, RPromise promise, RedisCommand> command, R res) {
if (isRedissonReferenceSupportEnabled()) {
handleReference(promise, res);
} else {
promise.trySuccess(res);
}
}
private void handleReference(RPromise mainPromise, R res) {
mainPromise.trySuccess((R) tryHandleReference(res));
}
protected Object tryHandleReference(Object o) {
boolean hasConversion = false;
if (o instanceof List) {
List r = (List) o;
for (int i = 0; i < r.size(); i++) {
Object ref = tryHandleReference0(r.get(i));
if (ref != r.get(i)) {
r.set(i, ref);
}
}
return o;
} else if (o instanceof Set) {
Set set, r = (Set) o;
boolean useNewSet = o instanceof LinkedHashSet;
try {
set = (Set) o.getClass().getConstructor().newInstance();
} catch (Exception exception) {
set = new LinkedHashSet();
}
for (Object i : r) {
Object ref = tryHandleReference0(i);
//Not testing for ref changes because r.add(ref) below needs to
//fail on the first iteration to be able to perform fall back
//if failure happens.
//
//Assuming the failure reason is systematic such as put method
//is not supported or implemented, and not an occasional issue
//like only one element fails.
if (useNewSet) {
set.add(ref);
} else {
try {
r.add(ref);
set.add(i);
} catch (Exception e) {
//r is not supporting add operation, like
//LinkedHashMap$LinkedEntrySet and others.
//fall back to use a new set.
useNewSet = true;
set.add(ref);
}
}
hasConversion |= ref != i;
}
if (!hasConversion) {
return o;
} else if (useNewSet) {
return set;
} else if (!set.isEmpty()) {
r.removeAll(set);
}
return o;
} else if (o instanceof Map) {
Map r = (Map) o;
for (Map.Entry e : r.entrySet()) {
if (e.getKey() instanceof RedissonReference
|| e.getValue() instanceof RedissonReference) {
Object key = e.getKey();
Object value = e.getValue();
if (e.getKey() instanceof RedissonReference) {
key = fromReference(e.getKey());
r.remove(e.getKey());
}
if (e.getValue() instanceof RedissonReference) {
value = fromReference(e.getValue());
}
r.put(key, value);
}
}
return o;
} else if (o instanceof ListScanResult) {
tryHandleReference(((ListScanResult) o).getValues());
return o;
} else if (o instanceof MapScanResult) {
MapScanResult scanResult = (MapScanResult) o;
Map oldMap = ((MapScanResult) o).getMap();
Map map = (Map) tryHandleReference(oldMap);
if (map != oldMap) {
MapScanResult newScanResult
= new MapScanResult(scanResult.getPos(), map);
newScanResult.setRedisClient(scanResult.getRedisClient());
return newScanResult;
} else {
return o;
}
} else {
return tryHandleReference0(o);
}
}
private Object tryHandleReference0(Object o) {
if (o instanceof RedissonReference) {
return fromReference(o);
} else if (o instanceof ScoredEntry && ((ScoredEntry) o).getValue() instanceof RedissonReference) {
ScoredEntry> se = ((ScoredEntry>) o);
return new ScoredEntry(se.getScore(), fromReference(se.getValue()));
} else if (o instanceof Map.Entry) {
Map.Entry old = (Map.Entry) o;
Object key = tryHandleReference0(old.getKey());
Object value = tryHandleReference0(old.getValue());
return value != old.getValue() || key != old.getKey()
? new AbstractMap.SimpleEntry(key, value)
: o;
} else {
return o;
}
}
private Object fromReference(Object res) {
try {
if (redisson != null) {
return RedissonObjectFactory.fromReference(redisson, (RedissonReference) res);
}
if (redissonReactive != null) {
return RedissonObjectFactory.fromReference(redissonReactive, (RedissonReference) res);
}
return RedissonObjectFactory.fromReference(redissonRx, (RedissonReference) res);
} catch (Exception exception) {
throw new IllegalStateException(exception);
}
}
protected void sendCommand(final AsyncDetails details, final RedisConnection connection) {
if (details.getSource().getRedirect() == Redirect.ASK) {
List> list = new ArrayList>(2);
RPromise promise = new RedissonPromise();
list.add(new CommandData