All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.datastax.driver.core.AbstractReconnectionHandler Maven / Gradle / Ivy

Go to download

A driver for Apache Cassandra 1.2+ that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol.

There is a newer version: 4.0.0
Show newest version
/*
 * Copyright DataStax, Inc.
 *
 * 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 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 java.util.concurrent.CountDownLatch;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy