/**
* 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.util.Iterator;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Keep track of how often a node replies with a HTimeoutException. If we go
* past the threshold of [timeoutCounter] timeouts within [timeWindow] milliseconds,
* then we mark the node as suspended. (10 timeouts within 500ms by default)
*
* Periodically check the suspended nodes list every retryDelayInSeconds. If
* the node has been suspended longer than nodeSuspensionDurationInSeconds,
* then we unsuspend, placing it back in the available pool. (10 second
* suspension retried every 10 seconds by default).
*
* @author zznate
*/
public class HostTimeoutTracker extends BackgroundCassandraHostService {
private static final Logger log = LoggerFactory.getLogger(HostTimeoutTracker.class);
private ConcurrentHashMap<CassandraHost, LinkedBlockingQueue<Long>> timeouts;
private ConcurrentHashMap<CassandraHost, Long> suspended;
private int timeoutCounter;
private int timeoutWindow;
private int nodeSuspensionDurationInSeconds;
public static final int DEF_TIMEOUT_COUNTER = 10;
public static final int DEF_TIMEOUT_WINDOW = 500;
public static final int DEF_NODE_SUSPENSION_DURATION_IN_SECONDS = 10;
public static final int DEF_NODE_UNSUSPEND_CHECK_DELAY_IN_SECONDS = 10;
public HostTimeoutTracker(HConnectionManager connectionManager,
CassandraHostConfigurator cassandraHostConfigurator) {
super(connectionManager, cassandraHostConfigurator);
retryDelayInSeconds = cassandraHostConfigurator.getHostTimeoutUnsuspendCheckDelay();
timeouts = new ConcurrentHashMap<CassandraHost, LinkedBlockingQueue<Long>>();
suspended = new ConcurrentHashMap<CassandraHost, Long>();
sf = executor.scheduleWithFixedDelay(new Unsuspender(), retryDelayInSeconds,retryDelayInSeconds, TimeUnit.SECONDS);
timeoutCounter = cassandraHostConfigurator.getHostTimeoutCounter();
timeoutWindow = cassandraHostConfigurator.getHostTimeoutWindow();
nodeSuspensionDurationInSeconds = cassandraHostConfigurator.getHostTimeoutSuspensionDurationInSeconds();
}
public boolean checkTimeout(CassandraHost cassandraHost) {
timeouts.putIfAbsent(cassandraHost, new LinkedBlockingQueue<Long>());
long currentTimeMillis = System.currentTimeMillis();
timeouts.get(cassandraHost).add(currentTimeMillis);
boolean timeout = false;
// if there are 3 timeouts within 500ms, return false
if ( timeouts.get(cassandraHost).size() > timeoutCounter) {
Long last = timeouts.get(cassandraHost).remove();
if (last.longValue() < (currentTimeMillis - timeoutWindow)) {
timeout = true;
connectionManager.suspendCassandraHost(cassandraHost);
suspended.putIfAbsent(cassandraHost, currentTimeMillis);
}
}
return timeout;
}
class Unsuspender implements Runnable {
@Override
public void run() {
for (Iterator<Entry<CassandraHost,Long>> iterator = suspended.entrySet().iterator(); iterator.hasNext();) {
Entry<CassandraHost,Long> vals = iterator.next();
if ( vals.getValue() < (System.currentTimeMillis() - (nodeSuspensionDurationInSeconds * 1000)) ) {
connectionManager.unsuspendCassandraHost(vals.getKey());
iterator.remove();
}
}
}
}
@Override
void applyRetryDelay() {
}
@Override
void shutdown() {
log.info("Shutting down HostTimeoutTracker");
if ( sf != null )
sf.cancel(true);
if ( executor != null )
executor.shutdownNow();
log.info("HostTimeTracker shutdown complete.");
}
}