package com.lambdaworks.redis.cluster;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import com.lambdaworks.redis.resource.ClientResources;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* @author Mark Paluch
*/
class ClusterTopologyRefreshScheduler implements Runnable, ClusterEventListener {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ClusterTopologyRefreshScheduler.class);
private static final ClusterTopologyRefreshOptions FALLBACK_OPTIONS = ClusterTopologyRefreshOptions.create();
private final RedisClusterClient redisClusterClient;
private final ClientResources clientResources;
private final ClusterTopologyRefreshTask clusterTopologyRefreshTask;
private final AtomicReference<Timeout> timeoutRef = new AtomicReference<>();
ClusterTopologyRefreshScheduler(RedisClusterClient redisClusterClient, ClientResources clientResources) {
this.redisClusterClient = redisClusterClient;
this.clientResources = clientResources;
this.clusterTopologyRefreshTask = new ClusterTopologyRefreshTask(redisClusterClient);
}
@Override
public void run() {
logger.debug("ClusterTopologyRefreshScheduler.run()");
if (isEventLoopActive() && redisClusterClient.getClusterClientOptions() != null) {
if (!redisClusterClient.getClusterClientOptions().isRefreshClusterView()) {
logger.debug("Periodic ClusterTopologyRefresh is disabled");
return;
}
} else {
logger.debug("Periodic ClusterTopologyRefresh is disabled");
return;
}
clientResources.eventExecutorGroup().submit(clusterTopologyRefreshTask);
}
private void indicateTopologyRefreshSignal() {
logger.debug("ClusterTopologyRefreshScheduler.indicateTopologyRefreshSignal()");
if (!acquireTimeout()) {
return;
}
if (isEventLoopActive() && redisClusterClient.getClusterClientOptions() != null) {
clientResources.eventExecutorGroup().submit(clusterTopologyRefreshTask);
} else {
logger.debug("Adaptive ClusterTopologyRefresh is disabled");
}
}
/**
* Check if the {@link EventExecutorGroup} is active
*
* @return false if the worker pool is terminating, shutdown or terminated
*/
protected boolean isEventLoopActive() {
EventExecutorGroup eventExecutors = clientResources.eventExecutorGroup();
if (eventExecutors.isShuttingDown() || eventExecutors.isShutdown() || eventExecutors.isTerminated()) {
return false;
}
return true;
}
private boolean acquireTimeout() {
Timeout existingTimeout = timeoutRef.get();
if (existingTimeout != null) {
if (!existingTimeout.isExpired()) {
return false;
}
}
ClusterTopologyRefreshOptions refreshOptions = getClusterTopologyRefreshOptions();
Timeout timeout = new Timeout(refreshOptions.getAdaptiveRefreshTimeout(),
refreshOptions.getAdaptiveRefreshTimeoutUnit());
if (timeoutRef.compareAndSet(existingTimeout, timeout)) {
return true;
}
return false;
}
@Override
public void onAskRedirection() {
if (isEnabled(ClusterTopologyRefreshOptions.RefreshTrigger.ASK_REDIRECT)) {
indicateTopologyRefreshSignal();
}
}
@Override
public void onMovedRedirection() {
if (isEnabled(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT)) {
indicateTopologyRefreshSignal();
}
}
@Override
public void onReconnection(int attempt) {
if (isEnabled(ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)
&& attempt >= getClusterTopologyRefreshOptions().getRefreshTriggersReconnectAttempts()) {
indicateTopologyRefreshSignal();
}
}
private ClusterTopologyRefreshOptions getClusterTopologyRefreshOptions() {
ClusterClientOptions clusterClientOptions = redisClusterClient.getClusterClientOptions();
if (clusterClientOptions != null) {
return clusterClientOptions.getTopologyRefreshOptions();
}
return FALLBACK_OPTIONS;
}
private boolean isEnabled(ClusterTopologyRefreshOptions.RefreshTrigger refreshTrigger) {
return getClusterTopologyRefreshOptions().getAdaptiveRefreshTriggers().contains(refreshTrigger);
}
/**
* Value object to represent a timeout.
*
* @author Mark Paluch
* @since 4.2
*/
private class Timeout {
private final long expiresMs;
public Timeout(long timeout, TimeUnit timeUnit) {
this.expiresMs = System.currentTimeMillis() + timeUnit.toMillis(timeout);
}
public boolean isExpired() {
return expiresMs < System.currentTimeMillis();
}
public long remaining() {
long diff = expiresMs - System.currentTimeMillis();
if (diff > 0) {
return diff;
}
return 0;
}
}
private static class ClusterTopologyRefreshTask implements Runnable {
private final RedisClusterClient redisClusterClient;
public ClusterTopologyRefreshTask(RedisClusterClient redisClusterClient) {
this.redisClusterClient = redisClusterClient;
}
public void run() {
if (logger.isDebugEnabled()) {
logger.debug("ClusterTopologyRefreshTask requesting partitions from {}",
redisClusterClient.getTopologyRefreshSource());
}
redisClusterClient.reloadPartitions();
}
}
}