package com.lambdaworks.redis.protocol;
import java.net.SocketAddress;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import com.lambdaworks.redis.ClientOptions;
import com.lambdaworks.redis.RedisChannelInitializer;
import com.lambdaworks.redis.internal.LettuceAssert;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.util.internal.logging.InternalLogLevel;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* @author Mark Paluch
*/
class ReconnectionHandler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReconnectionHandler.class);
private final Supplier<SocketAddress> socketAddressSupplier;
private final Bootstrap bootstrap;
private final ClientOptions clientOptions;
private TimeUnit timeoutUnit = TimeUnit.SECONDS;
private long timeout = 60;
private volatile ChannelFuture currentFuture;
private boolean reconnectSuspended;
ReconnectionHandler(ClientOptions clientOptions, Bootstrap bootstrap,
Supplier<SocketAddress> socketAddressSupplier) {
LettuceAssert.notNull(socketAddressSupplier, "SocketAddressSupplier must not be null");
LettuceAssert.notNull(clientOptions, "ClientOptions must not be null");
LettuceAssert.notNull(bootstrap, "Bootstrap must not be null");
this.socketAddressSupplier = socketAddressSupplier;
this.bootstrap = bootstrap;
this.clientOptions = clientOptions;
}
protected boolean reconnect(InternalLogLevel infoLevel) throws Exception {
SocketAddress remoteAddress = socketAddressSupplier.get();
try {
long timeLeft = timeoutUnit.toNanos(timeout);
long start = System.nanoTime();
logger.debug("Reconnecting to Redis at {}", remoteAddress);
currentFuture = bootstrap.connect(remoteAddress);
if (!currentFuture.await(timeLeft, TimeUnit.NANOSECONDS)) {
if (currentFuture.isCancellable()) {
currentFuture.cancel(true);
}
throw new TimeoutException("Reconnection attempt exceeded timeout of " + timeout + " " + timeoutUnit);
}
currentFuture.sync();
Channel channel = currentFuture.channel();
RedisChannelInitializer channelInitializer = channel.pipeline().get(RedisChannelInitializer.class);
CommandHandler<?, ?> commandHandler = channel.pipeline().get(CommandHandler.class);
if (channelInitializer == null) {
logger.warn("Reconnection attempt without a RedisChannelInitializer in the channel pipeline");
close(channel);
return false;
}
if (commandHandler == null) {
logger.warn("Reconnection attempt without a CommandHandler in the channel pipeline");
close(channel);
return false;
}
try {
timeLeft -= System.nanoTime() - start;
channelInitializer.channelInitialized().get(Math.max(0, timeLeft), TimeUnit.NANOSECONDS);
if (logger.isDebugEnabled()) {
logger.log(infoLevel, "Reconnected to {}, Channel {}", remoteAddress,
ChannelLogDescriptor.logDescriptor(channel));
} else {
logger.log(infoLevel, "Reconnected to {}", remoteAddress);
}
return true;
} catch (TimeoutException e) {
channelInitializer.channelInitialized().cancel(true);
} catch (Exception e) {
if (clientOptions.isCancelCommandsOnReconnectFailure()) {
commandHandler.reset();
}
if (clientOptions.isSuspendReconnectOnProtocolFailure()) {
logger.error("Cannot initialize channel. Disabling autoReconnect", e);
setReconnectSuspended(true);
} else {
logger.error("Cannot initialize channel.", e);
throw e;
}
}
} finally {
currentFuture = null;
}
return false;
}
private void close(Channel channel) {
if (channel != null && channel.isOpen()) {
channel.close();
}
}
public boolean isReconnectSuspended() {
return reconnectSuspended;
}
public void setReconnectSuspended(boolean reconnectSuspended) {
this.reconnectSuspended = reconnectSuspended;
}
public TimeUnit getTimeoutUnit() {
return timeoutUnit;
}
public void setTimeoutUnit(TimeUnit timeoutUnit) {
this.timeoutUnit = timeoutUnit;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public void prepareClose() {
if (currentFuture != null && !currentFuture.isDone()) {
currentFuture.cancel(true);
}
}
ClientOptions getClientOptions() {
return clientOptions;
}
}