/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.drivers.jdbc.pool.cassandra.connection;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.cassandra.cql.jdbc.CassandraDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CassandraHostRetryService extends BackgroundCassandraHostService {
private static Logger log = LoggerFactory.getLogger(CassandraHostRetryService.class);
public static final int DEF_QUEUE_SIZE = -1;
public static final int DEF_RETRY_DELAY = 10;
private final LinkedBlockingQueue<CassandraHost> downedHostQueue;
public CassandraHostRetryService(HConnectionManager connectionManager,
CassandraHostConfigurator cassandraHostConfigurator) {
super(connectionManager, cassandraHostConfigurator);
this.retryDelayInSeconds = cassandraHostConfigurator.getRetryDownedHostsDelayInSeconds();
downedHostQueue = new LinkedBlockingQueue<CassandraHost>(cassandraHostConfigurator.getRetryDownedHostsQueueSize() < 1
? Integer.MAX_VALUE : cassandraHostConfigurator.getRetryDownedHostsQueueSize());
sf = executor.scheduleWithFixedDelay(new RetryRunner(), this.retryDelayInSeconds,this.retryDelayInSeconds, TimeUnit.SECONDS);
log.info("Downed Host Retry service started with queue size {} and retry delay {}s",
cassandraHostConfigurator.getRetryDownedHostsQueueSize(),
retryDelayInSeconds);
}
@Override
void shutdown() {
log.info("Downed Host retry shutdown hook called");
if ( sf != null ) {
sf.cancel(true);
}
if ( executor != null ) {
executor.shutdownNow();
}
log.info("Downed Host retry shutdown complete");
}
public void add(final CassandraHost cassandraHost) {
downedHostQueue.add(cassandraHost);
if ( log.isInfoEnabled() ) {
log.info("Host detected as down was added to retry queue: {}", cassandraHost.getName());
}
//schedule a check of this host immediately,
executor.submit(new Runnable() {
@Override
public void run() {
if(downedHostQueue.contains(cassandraHost) && verifyConnection(cassandraHost)) {
connectionManager.addCassandraHost(cassandraHost);
downedHostQueue.remove(cassandraHost);
return;
}
}
});
}
public boolean remove(CassandraHost cassandraHost) {
return downedHostQueue.remove(cassandraHost);
}
public boolean contains(CassandraHost cassandraHost) {
return downedHostQueue.contains(cassandraHost);
}
public Set<CassandraHost> getDownedHosts() {
return Collections.unmodifiableSet(new HashSet<CassandraHost>(downedHostQueue));
}
@Override
public void applyRetryDelay() {
sf.cancel(false);
executor.schedule(new RetryRunner(), retryDelayInSeconds, TimeUnit.SECONDS);
}
public void flushQueue() {
downedHostQueue.clear();
log.info("Downed Host retry queue flushed.");
}
class RetryRunner implements Runnable {
@Override
public void run() {
if( downedHostQueue.isEmpty()) {
log.debug("Retry service fired... nothing to do.");
return;
}
Iterator<CassandraHost> iter = downedHostQueue.iterator();
while( iter.hasNext() ) {
CassandraHost cassandraHost = iter.next();
if( cassandraHost == null ) {
continue;
}
boolean reconnected = verifyConnection(cassandraHost);
log.info("Downed Host retry status {} with host: {}", reconnected, cassandraHost.getName());
if ( reconnected ) {
connectionManager.addCassandraHost(cassandraHost);
//we can't call iter.remove() based on return value of connectionManager.addCassandraHost, since
//that returns false if an error occurs, or if the host already exists
if(connectionManager.getHosts().contains(cassandraHost)) {
iter.remove();
}
}
}
}
}
private boolean verifyConnection(CassandraHost cassandraHost) {
if ( cassandraHost == null ) {
return false;
}
boolean found = false;
CassandraDataSource ds = new CassandraDataSource(cassandraHost.getHost(),
cassandraHost.getPort(),
cassandraHost.getKeyspaceName(),
cassandraHost.getUser(),
cassandraHost.getPassword());
Connection conn = null;
try {
conn = ds.getConnection();
// May be add some queries
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Downed {} host still appears to be down: {}", cassandraHost, e);
} else {
log.info("Downed {} host still appears to be down: {}", cassandraHost, e.getMessage());
}
} finally {
try {
conn.close();
} catch (Exception e) {
// Ignoring this. Nothing we can do.
}
}
ds = null;
conn = null;
return found;
}
}