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 2016 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.Collections;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.redisson.RedissonReference;
import org.redisson.RedissonShutdownException;
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.RPromise;
import org.redisson.misc.RedissonObjectFactory;
import org.redisson.misc.RedissonPromise;
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 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 volatile boolean executed;
public CommandBatchService(ConnectionManager connectionManager) {
super(connectionManager);
}
@Override
protected void async(boolean readOnlyMode, NodeSource nodeSource,
Codec codec, RedisCommand command, Object[] params, RPromise mainPromise, int attempt) {
if (executed) {
throw new IllegalStateException("Batch already has been executed!");
}
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);
}
if (isRedissonReferenceSupportEnabled()) {
for (int i = 0; i < params.length; i++) {
RedissonReference reference = RedissonObjectFactory.toReference(connectionManager.getCfg(), params[i]);
if (reference != null) {
params[i] = reference;
}
}
}
BatchCommandData commandData = new BatchCommandData(mainPromise, codec, command, params, index.incrementAndGet());
entry.getCommands().add(commandData);
}
public BatchResult> execute() {
RFuture> f = executeAsync(0, 0, false, 0, 0, 0);
return get(f);
}
public BatchResult> execute(int syncSlaves, long syncTimeout, boolean noResult, long responseTimeout, int retryAttempts, long retryInterval) {
RFuture> f = executeAsync(syncSlaves, syncTimeout, noResult, responseTimeout, retryAttempts, retryInterval);
return get(f);
}
public RFuture executeAsyncVoid() {
final RedissonPromise promise = new RedissonPromise();
RFuture> res = executeAsync(0, 0, false, 0, 0, 0);
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(0, 0, false, 0, 0, 0);
}
public RFuture executeAsync(int syncSlaves, long syncTimeout, boolean skipResult, long responseTimeout, int retryAttempts, long retryInterval) {
if (executed) {
throw new IllegalStateException("Batch already executed!");
}
if (commands.isEmpty()) {
return RedissonPromise.newSucceededFuture(null);
}
executed = true;
if (skipResult) {
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 (syncSlaves > 0) {
for (Entry entry : commands.values()) {
BatchCommandData, ?> waitCommand = new BatchCommandData(RedisCommands.WAIT, new Object[] { syncSlaves, syncTimeout }, index.incrementAndGet());
entry.getCommands().add(waitCommand);
}
}
RPromise resultPromise;
RPromise voidPromise = new RedissonPromise();
if (skipResult) {
voidPromise.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
commands = null;
}
});
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;
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)) {
Object entryResult = commandEntry.getPromise().getNow();
entryResult = tryHandleReference(entryResult);
responses.add(entryResult);
} else {
syncedSlaves = (Integer) commandEntry.getPromise().getNow();
}
}
BatchResult result = new BatchResult(responses, syncedSlaves);
promise.trySuccess(result);
commands = null;
}
});
resultPromise = (RPromise) promise;
}
AtomicInteger slots = new AtomicInteger(commands.size());
for (java.util.Map.Entry e : commands.entrySet()) {
execute(e.getValue(), new NodeSource(e.getKey()), voidPromise, slots, 0, skipResult, responseTimeout, retryAttempts, retryInterval);
}
return resultPromise;
}
private void execute(final Entry entry, final NodeSource source, final RPromise mainPromise, final AtomicInteger slots,
final int attempt, final boolean noResult, final long responseTimeout, final int retryAttempts, final long retryInterval) {
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 (retryAttempts > 0) {
attempts = retryAttempts;
} else {
attempts = connectionManager.getConfig().getRetryAttempts();
}
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)) {
connectionManager.getShutdownLatch().release();
} else {
if (connectionFuture.isSuccess()) {
if (details.getWriteFuture() == null || !details.getWriteFuture().isDone()) {
if (details.getAttempt() == attempts) {
if (details.getWriteFuture().cancel(false)) {
if (details.getException() == null) {
details.setException(new RedisTimeoutException("Unable to send batch after " + connectionManager.getConfig().getRetryAttempts() + " retry attempts"));
}
attemptPromise.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 (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, noResult, responseTimeout, retryAttempts, retryInterval);
}
};
long interval = connectionManager.getConfig().getRetryInterval();
if (retryInterval > 0) {
interval = retryInterval;
}
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, noResult, responseTimeout, attempts);
}
});
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, noResult, responseTimeout, retryAttempts, retryInterval);
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, noResult, responseTimeout, retryAttempts, retryInterval);
return;
}
if (future.cause() instanceof RedisLoadingException) {
entry.clearErrors();
execute(entry, source, mainPromise, slots, attempt, noResult, responseTimeout, retryAttempts, retryInterval);
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, noResult, responseTimeout, retryAttempts, retryInterval);
}
}, 1, TimeUnit.SECONDS);
return;
}
free(entry);
if (future.isSuccess()) {
if (slots.decrementAndGet() == 0) {
mainPromise.trySuccess(future.getNow());
}
} else {
mainPromise.tryFailure(future.cause());
}
}
});
}
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, boolean noResult, 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) {
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();
List> list = new ArrayList>(entry.getCommands().size() + 1);
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)) {
// skip successful commands
continue;
}
list.add(c);
}
ChannelFuture future = connection.send(new CommandsData(attemptPromise, list, noResult));
details.setWriteFuture(future);
details.getWriteFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
checkWriteFuture(entry, attemptPromise, details, connection, future, noResult, responseTimeout, attempts);
}
});
releaseConnection(source, connFuture, entry.isReadOnlyMode(), attemptPromise, details);
}
protected boolean isWaitCommand(BatchCommandData, ?> c) {
return c.getCommand().getName().equals(RedisCommands.WAIT.getName());
}
}