/*
* Copyright 2009 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.db;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.sql.DataSource;
import ome.conditions.DatabaseBusyException;
import ome.util.messages.UserSignalMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.jdbc.datasource.DelegatingDataSource;
/**
* {@link DataSource} delegate which wraps the
*/
public class SelfCorrectingDataSource extends DelegatingDataSource
implements ApplicationListener<UserSignalMessage> {
private final static Logger log = LoggerFactory
.getLogger(SelfCorrectingDataSource.class);
/**
* Length of time that errors are used in the calculation of
* {@link DatabaseBusyException#backOff}
*/
private final long errorTimeout;
private final int maxRetries;
private final long maxBackOff;
private final List<Long> errorTimes = new ArrayList<Long>();
public SelfCorrectingDataSource(DataSource delegate,
long timeoutInMilliseconds) {
this(delegate, timeoutInMilliseconds, 3, 10 * 1000L);
}
public SelfCorrectingDataSource(DataSource delegate,
long timeoutInMilliseconds, int maxRetries, long maxBackOff) {
super(delegate);
this.errorTimeout = timeoutInMilliseconds;
this.maxRetries = maxRetries;
this.maxBackOff = maxBackOff;
}
public Connection getConnection() throws SQLException {
return callWithRetries(null, null, false);
}
public Connection getConnection(String username, String password)
throws SQLException {
return callWithRetries(username, password, true);
}
/**
* Handles the USR1 posix signal by calling close on the underlying
* {@link #getTargetDataSource() data source} via reflection. The
* assumption is that the next call to any methods will re-initialize
* the data source. This is the case with
* bitronix.tm.resource.jdbc.PoolingDataSource
*
* @see <a href="http://trac.openmicroscopy.org/ome/ticket/4210">ticket:4210</a>
*/
public void onApplicationEvent(UserSignalMessage usm) {
if (usm.signal == 1) {
log.info("Received USR" + usm.signal + " - calling close()");
try {
Method m = getTargetDataSource().getClass().getMethod("close");
m.invoke(getTargetDataSource());
} catch (Exception e) {
log.error("Failed to close " + getTargetDataSource(), e);
}
}
}
// Helpers
// =========================================================================
protected Connection callWithRetries(String username, String password,
boolean useArgs) throws SQLException {
int retries = 0;
while (true) {
try {
return call(username, password, useArgs);
} catch (SQLException sql) {
long backOff = markAndSweep();
retries++;
if (retries < maxRetries) {
log.info("Sleeping for " + backOff + " then retry: "
+ retries);
try {
Thread.sleep(backOff);
continue;
} catch (InterruptedException e) {
// Ok. Outer while loop while catch us.
}
}
log.error("Failed to acquire connection after retries="
+ maxRetries, sql);
throw new DatabaseBusyException("Cannot acquire connection",
backOff);
}
}
}
protected Connection call(String username, String password, boolean useArgs)
throws SQLException {
if (useArgs) {
return super.getConnection(username, password);
} else {
return super.getConnection();
}
}
/**
* First removes all entries in {@link #errorTimes} that are older than some
* given time and then uses the remaining number of errors to determine the
* backoff : (#^1/2)*1000 milliseconds.
*/
protected long markAndSweep() {
final long timeAgo = System.currentTimeMillis() - errorTimeout;
synchronized (errorTimes) {
int location = Collections.binarySearch(errorTimes, timeAgo);
log.info("Found location in errorTimes: " + location);
if (location < 0) {
location = -location - 1; // search returns -insertion-1
}
List<Long> subList = new ArrayList<Long>(errorTimes.subList(
location, errorTimes.size()));
int eSize = errorTimes.size();
int sSize = subList.size();
log.info("Removing " + (eSize - sSize) + " from errorTimes");
errorTimes.clear();
errorTimes.addAll(subList);
log.warn("Registering error with list: Current size: " + sSize);
errorTimes.add(System.currentTimeMillis());
return calculateBackOff(sSize);
}
}
protected long calculateBackOff(int numberOfErrors) {
long backOff = 1000L * Math.round(Math.sqrt(numberOfErrors));
if (backOff > maxBackOff) {
return maxBackOff;
} else {
return backOff;
}
}
}