/* * (C) Copyright IBM Corp. 2008 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.Stack; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.ibm.gaiandb.diags.GDBMessages; /** * @author DavidVyvyan */ public class DatabaseConnector implements Runnable { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2008"; private static final Logger logger = new Logger( "DatabaseConnector", 35 ); // Max response time accepted for a connection attempt - note that this should // take account of the JDBC timeout of 2 minutes as the number of uninterrupted threads will grow for // every new connection attempted in those 2 minutes. // private static final int LONGEST_EXPECTED_TIME_FOR_CONNECTION_ATTEMPT_MS = 10000; // i.e. there could be 120000 / 10000 = 12 concurrent failing connections // Stagger the connection attempts into the following duration evenly (unless JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS is set) //private static final int EXPECTED_JDBC_CONNECTION_TIMEOUT_MS = 150000; // 2m30s // jdbc url -> threads connecting to it // This is used to cap the number of connections being sought concurrently on a single url. // Indeed there is no need for this number to be too high - if it is it means connections are all timing out anyway // and each new attempt is just wasting resources. private static ConcurrentMap<String, Vector<Thread>> connectionsInProgress = new ConcurrentHashMap<String, Vector<Thread>>(); // private static ConcurrentMap<String, Long> latestConnectionAttemptTimes = new ConcurrentHashMap<String, Long>(); // The list of connecting threads for this instance of DatabaseConnector, mapped 121 with urls. private Vector<Thread> connectingThreads = null; // private static final int MAX_CONCURRENT_CONNECTION_ATTEMPTS_PER_SOURCE = 1; private final String url; private final String usr; private final String pwd; private Stack<Connection> connectionPool = null; // private String exceptionMsg = null; public DatabaseConnector( String url, String usr, String pwd ) { this.url = url; this.usr = usr; this.pwd = pwd; } public ResultSetMetaData getTableMetaData( String table ) throws SQLException { ResultSet rs = null; if ( null == usr || 0 == usr.length() ) { logger.logInfo( "Preparing JDBC Statement and getting RSMD with no authentication parms"); // Do not use getMetaData() on a non executed prepared statement - Oracle does not allow it // return DriverManager.getConnection( url ).prepareStatement(sql).getMetaData(); rs = DriverManager.getConnection( url ).createStatement(). executeQuery("select * from " + table + " where 0=1"); } else { logger.logInfo( "Preparing JDBC Statement and getting RSMD with authentication parms for usr " + usr); // System.out.println( "Preparing JDBC Statement, url " + url + ", usr " + usr + ", pwd " + pwd ); rs = DriverManager.getConnection( url, usr, pwd ).createStatement(). executeQuery("select * from " + table + " where 0=1"); } return rs.getMetaData(); } public Connection getConnection() throws SQLException { if ( null == usr || 0 == usr.length() ) { logger.logDetail( "Creating JDBC Connection for url: " + url + ", concurrent attempts: " + (null == connectingThreads ? 1 : connectingThreads.size())); return DriverManager.getConnection( url ); } else { logger.logDetail( "Creating JDBC Connection for usr: " + usr + ", url: " + url + ", concurrent attempts: " + (null == connectingThreads ? 1 : connectingThreads.size())); return DriverManager.getConnection( url, usr, pwd ); } } /** * Get a jdbc statement and put it in the given pool * Once the statement is obtained, return. * If the statement is not obtained within timeoutMs ms, return anyway while it keeps trying to get it. */ public Connection getConnectionWithinTimeoutOrToPoolAsynchronously( Stack<Connection> connectionPool, long timeoutMs ) { long t0 = System.currentTimeMillis(); synchronized( this.connectionPool = connectionPool ) { do { connectingThreads = connectionsInProgress.get( url ); if ( null == connectingThreads ) connectingThreads = new Vector<Thread>(); int numConcurrentAttempts = connectingThreads.size(); // int maxConcurrentConnectionAttempts = GaianDBConfig.getMaxConcurrentConnectionAttempts(); // this must be 1 if ( numConcurrentAttempts < 1 ) //maxConcurrentConnectionAttempts && numConcurrentAttempts < 3 ) launchConnectionThread( timeoutMs ); // else if ( numConcurrentAttempts < maxConcurrentConnectionAttempts ) { // // long timeSinceLatestConnectionAttempt = System.currentTimeMillis() - latestConnectionAttemptTimes.get(url); // // int jdbcConnectionAttemptTimeout = GaianDBConfig.getConnectionAttemptTimeout(); // if ( 0 > jdbcConnectionAttemptTimeout ) // jdbcConnectionAttemptTimeout = EXPECTED_JDBC_CONNECTION_TIMEOUT_MS / (maxConcurrentConnectionAttempts-3); // // // Stagger connection attempts during the JDBC driver timeout (which is usually about 2min30secs for Derby) // // in case the destination is reachable again. // if ( timeSinceLatestConnectionAttempt > jdbcConnectionAttemptTimeout ) { // logger.logThreadWarning("Making new connection (currently pending: " + numConcurrentAttempts + // ", time since last attempt: " + timeSinceLatestConnectionAttempt + "ms), url:" + url); // // Thread t = (Thread) connectingThreads.get(0); // // t.interrupt(); // this will prob have no effect, we'll still be waiting 2mins on the jdbc socket timeout. // // // Now launch a new connection thread replacing the old one - the new attempt might have more chances. // launchConnectionThread( timeoutMs ); // } // // // Now wait for a connection to be established... (whether or not we launched a connection attempt) // } else { logger.logThreadImportant( "Reached max concurrent connection attempts: " + 1 //maxConcurrentConnectionAttempts + ", for url: " + url + ( 1 > timeoutMs ? " - abandoning as timeout is 0ms" : " - waiting " + timeoutMs + "ms") ); // for connection or timeout"); //+ " (killing 1)"); //" (ignoring request)"); } if ( 1 > timeoutMs ) return null; try { this.connectionPool.wait( timeoutMs ); } // releases pool synchro lock while waiting catch (InterruptedException e) { // unexpected logger.logException(GDBMessages.ENGINE_CONN_WAIT_INTERRUPTED_ERROR, "Caught InterruptedException whilst waiting for DB Connection: ", e ); } if ( !this.connectionPool.isEmpty() ) return this.connectionPool.pop(); } while ( ( timeoutMs -= System.currentTimeMillis() - t0 ) > 0 ); // false ); // } return null; // No connection could be obtained within the timeout. If/when it is obtained, it will be left in the pool. } private void launchConnectionThread( long timeoutMs ) { int tIndex = connectingThreads.size() + 1; Thread t = new Thread(this, "Connector " + tIndex + " from " + Thread.currentThread().getName()); connectingThreads.add(t); logger.logThreadInfo( "Getting DB Connection for: " + url + " (" + tIndex + " concurrent, synchronous timeout " + timeoutMs + "ms)" ); t.start(); connectionsInProgress.put( url, connectingThreads ); } public void run() { long cstart = System.currentTimeMillis(); try { Connection c = getConnection(); connectionPool.push( c ); logger.logThreadImportant( "Obtained JDBC Connection " + c + " in " + (System.currentTimeMillis()-cstart) + " ms for: " + url + ", Pool size: " + connectionPool.size() ); } catch ( Exception e ) { String db2Info = null; logger.logThreadWarning(GDBMessages.ENGINE_JDBC_CONN_ATTEMPT_ERROR, "Failed JDBC Connection attempt in " + (System.currentTimeMillis()-cstart) + " ms for: " + url + ", cause: " + e + ( e instanceof SQLException && null!=url && url.startsWith("jdbc:db2:") && null!=(db2Info=Util.getDB2Msg((SQLException) e, false)) ? ", DB2 Info: " + db2Info : "" ) + "; Common issues: missing jdbc driver, network/database unavailability (e.g. firewall), incorrect user/password and/or insufficient database access rights" + (null!=url && url.startsWith("jdbc:derby") ? " (e.g. if derby.database.defaultConnectionMode=noAccess in derby.properties)" : "") // + "\n" + Util.getStackTraceDigest(e) ); } if ( connectingThreads.remove( Thread.currentThread() ) ) { // int maxConcurrentConnectionAttempts = GaianDBConfig.getMaxConcurrentConnectionAttempts(); // if ( connectingThreads.size()+1 == maxConcurrentConnectionAttempts ) // logger.logThreadImportant( "Dropped below max concurrent connection attempts: " + // maxConcurrentConnectionAttempts + " for url: " + url + " - new attemps can now be made"); } synchronized (connectionPool) { connectionPool.notifyAll(); } } }