com.datastax.driver.core.AbstractReconnectionHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dse-java-driver-core Show documentation
Show all versions of dse-java-driver-core Show documentation
A driver for DataStax Enterprise (DSE)
and Apache Cassandra 1.2+ clusters that works exclusively with the
Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol,
supporting DSE-specific features such as geospatial types, DSE Graph and DSE authentication.
/*
* Copyright (C) 2012-2017 DataStax Inc.
*
* This software can be used solely with DataStax Enterprise. Please consult the license at
* http://www.datastax.com/terms/datastax-dse-driver-license-terms
*/
package com.datastax.driver.core;
import com.datastax.driver.core.exceptions.AuthenticationException;
import com.datastax.driver.core.exceptions.ConnectionException;
import com.datastax.driver.core.exceptions.UnsupportedProtocolVersionException;
import com.datastax.driver.core.policies.ReconnectionPolicy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.ListenableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* Manages periodic reconnection attempts after a host has been marked down.
*
* Concurrent attempts are handled via the {@link #currentAttempt} reference passed to the constructor.
* For a given reference, only one handler will run at a given time. Additional handlers will cancel
* themselves if they find a previous handler running.
*
* This class is designed for concurrency, but instances must not be shared: each thread creates and
* starts its own private handler, all interactions happen through {@link #currentAttempt}.
*/
abstract class AbstractReconnectionHandler implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(AbstractReconnectionHandler.class);
private final String name;
private final ScheduledExecutorService executor;
private final ReconnectionPolicy.ReconnectionSchedule schedule;
/**
* The future that is exposed to clients, representing completion of the current active handler
*/
private final AtomicReference> currentAttempt;
@VisibleForTesting
final HandlerFuture handlerFuture = new HandlerFuture();
private final long initialDelayMs;
private final CountDownLatch ready = new CountDownLatch(1);
public AbstractReconnectionHandler(String name, ScheduledExecutorService executor, ReconnectionPolicy.ReconnectionSchedule schedule, AtomicReference> currentAttempt) {
this(name, executor, schedule, currentAttempt, -1);
}
public AbstractReconnectionHandler(String name, ScheduledExecutorService executor, ReconnectionPolicy.ReconnectionSchedule schedule, AtomicReference> currentAttempt, long initialDelayMs) {
this.name = name;
this.executor = executor;
this.schedule = schedule;
this.currentAttempt = currentAttempt;
this.initialDelayMs = initialDelayMs;
}
protected abstract Connection tryReconnect() throws ConnectionException, InterruptedException, UnsupportedProtocolVersionException, ClusterNameMismatchException;
protected abstract void onReconnection(Connection connection);
protected boolean onConnectionException(ConnectionException e, long nextDelayMs) {
return true;
}
protected boolean onUnknownException(Exception e, long nextDelayMs) {
return true;
}
// Retrying on authentication errors makes sense for applications that can update the credentials at runtime, we don't want to force them
// to restart.
protected boolean onAuthenticationException(AuthenticationException e, long nextDelayMs) {
return true;
}
// Retrying on these errors is unlikely to work
protected boolean onUnsupportedProtocolVersionException(UnsupportedProtocolVersionException e, long nextDelayMs) {
return false;
}
protected boolean onClusterNameMismatchException(ClusterNameMismatchException e, long nextDelayMs) {
return false;
}
public void start() {
long firstDelay = (initialDelayMs >= 0) ? initialDelayMs : schedule.nextDelayMs();
logger.debug("First reconnection scheduled in {}ms", firstDelay);
try {
handlerFuture.nextTry = executor.schedule(this, firstDelay, TimeUnit.MILLISECONDS);
while (true) {
ListenableFuture> previous = currentAttempt.get();
if (previous != null && !previous.isCancelled()) {
logger.debug("Found another already active handler, cancelling");
handlerFuture.cancel(false);
break;
}
if (currentAttempt.compareAndSet(previous, handlerFuture)) {
Host.statesLogger.debug("[{}] starting reconnection attempt", name);
break;
}
}
ready.countDown();
} catch (RejectedExecutionException e) {
// The executor has been shutdown, fair enough, just ignore
logger.debug("Aborting reconnection handling since the cluster is shutting down");
}
}
@Override
public void run() {
// Just make sure we don't start the first try too fast, in case we find out in start() that we need to cancel ourselves
try {
ready.await();
} catch (InterruptedException e) {
// This can happen at shutdown
Thread.currentThread().interrupt();
return;
}
if (handlerFuture.isCancelled()) {
logger.debug("Got cancelled, stopping");
return;
}
try {
onReconnection(tryReconnect());
handlerFuture.markAsDone();
currentAttempt.compareAndSet(handlerFuture, null);
logger.debug("Reconnection successful, cleared the future");
} catch (ConnectionException e) {
long nextDelay = schedule.nextDelayMs();
if (onConnectionException(e, nextDelay))
reschedule(nextDelay);
else
currentAttempt.compareAndSet(handlerFuture, null);
} catch (AuthenticationException e) {
logger.error(e.getMessage());
long nextDelay = schedule.nextDelayMs();
if (onAuthenticationException(e, nextDelay)) {
reschedule(nextDelay);
} else {
logger.error("Retries against {} have been suspended. It won't be retried unless the node is restarted.", e.getHost());
currentAttempt.compareAndSet(handlerFuture, null);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (UnsupportedProtocolVersionException e) {
logger.error(e.getMessage());
long nextDelay = schedule.nextDelayMs();
if (onUnsupportedProtocolVersionException(e, nextDelay)) {
reschedule(nextDelay);
} else {
logger.error("Retries against {} have been suspended. It won't be retried unless the node is restarted.", e.getHost());
currentAttempt.compareAndSet(handlerFuture, null);
}
} catch (ClusterNameMismatchException e) {
logger.error(e.getMessage());
long nextDelay = schedule.nextDelayMs();
if (onClusterNameMismatchException(e, nextDelay)) {
reschedule(nextDelay);
} else {
logger.error("Retries against {} have been suspended. It won't be retried unless the node is restarted.", e.address.getAddress());
currentAttempt.compareAndSet(handlerFuture, null);
}
} catch (Exception e) {
long nextDelay = schedule.nextDelayMs();
if (onUnknownException(e, nextDelay))
reschedule(nextDelay);
else
currentAttempt.compareAndSet(handlerFuture, null);
}
}
private void reschedule(long nextDelay) {
// If we got cancelled during the failed reconnection attempt that lead here, don't reschedule
if (handlerFuture.isCancelled()) {
currentAttempt.compareAndSet(handlerFuture, null);
return;
}
Host.statesLogger.debug("[{}] next reconnection attempt in {} ms", name, nextDelay);
handlerFuture.nextTry = executor.schedule(this, nextDelay, TimeUnit.MILLISECONDS);
}
// The future that the handler exposes to its clients via currentAttempt
@VisibleForTesting
static class HandlerFuture extends AbstractFuture {
// A future representing completion of the next task submitted to the executor
volatile ScheduledFuture> nextTry;
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// This is a check-then-act, so we may race with the scheduling of the first try, but in that case
// we'll re-check for cancellation when this first try starts running
if (nextTry != null) {
nextTry.cancel(mayInterruptIfRunning);
}
return super.cancel(mayInterruptIfRunning);
}
void markAsDone() {
super.set(null);
}
}
}