/* * This software is released under a licence similar to the Apache Software Licence. * See org.logicalcobwebs.proxool.package.html for details. * The latest version is available at http://proxool.sourceforge.net */ package org.logicalcobwebs.proxool; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.logicalcobwebs.concurrent.ReaderPreferenceReadWriteLock; import org.logicalcobwebs.concurrent.WriterPreferenceReadWriteLock; import org.logicalcobwebs.proxool.admin.Admin; import org.logicalcobwebs.proxool.util.FastArrayList; import org.quickbundle.config.RmConfig; import java.sql.Connection; import java.sql.SQLException; import java.util.*; /** * This is where most things happen. (In fact, probably too many things happen in this one * class). * @version $Revision: 1.86 $, $Date: 2007/01/25 23:38:24 $ * @author billhorsman * @author $Author: billhorsman $ (current maintainer) */ class ConnectionPool implements ConnectionPoolStatisticsIF { /** * Use this for messages that aren't useful for the pool specific log */ private static final Log LOG = LogFactory.getLog(ConnectionPool.class); /** * Here we deviate from the standard of using the classname for the log * name. Here we want to use the alias for the pool so that we can log * each pool to different places. So we have to instantiate the log later. */ private Log log; private ReaderPreferenceReadWriteLock connectionStatusReadWriteLock = new ReaderPreferenceReadWriteLock(); /** * If you want to shutdown the pool you should get a write lock on this. And if you use the pool then * get a read lock. This stops us trying to shutdown the pool whilst it is in use. Only, we don't want * to delay shutdown just because some greedy user has got a connection active. Shutdown should be * relatively immediate. So we don't ask for a read lock for the whole time that a connection is active. */ private WriterPreferenceReadWriteLock primaryReadWriteLock = new WriterPreferenceReadWriteLock(); private static final String[] STATUS_DESCRIPTIONS = {"NULL", "AVAILABLE", "ACTIVE", "OFFLINE"}; private static final String MSG_MAX_CONNECTION_COUNT = "Couldn't get connection because we are at maximum connection count and there are none available"; /** This is the pool itself */ private List proxyConnections; /** This is the "round robin" that makes sure we use all the connections */ private int nextAvailableConnection = 0; private long connectionsServedCount = 0; private long connectionsRefusedCount = 0; /** This keeps a count of how many connections there are in each state */ private int[] connectionCountByState = new int[4]; private ConnectionPoolDefinition definition; private CompositeConnectionListener compositeConnectionListener = new CompositeConnectionListener(); private CompositeStateListener compositeStateListener = new CompositeStateListener(); private long timeOfLastRefusal = 0; private int upState; private static boolean loggedLegend; private Admin admin; private boolean locked = false; private Date dateStarted = new Date(); private boolean connectionPoolUp = false; /** * This gets set during {@link #shutdown}. We use it to notify shutdown * that all connections are now non-active. */ private Thread shutdownThread; private Prototyper prototyper; /** * Initialised in {@link ConnectionPool#ConnectionPool constructor}. */ private ConnectionResetter connectionResetter; private ConnectionValidatorIF connectionValidator; protected ConnectionPool(ConnectionPoolDefinition definition) throws ProxoolException { // Use the FastArrayList for performance and thread safe // behaviour. We set its behaviour to "fast" (meaning reads are // unsynchronized, whilst writes are not). FastArrayList fal = new FastArrayList(); fal.setFast(true); proxyConnections = fal; log = LogFactory.getLog("org.logicalcobwebs.proxool." + definition.getAlias()); connectionResetter = new ConnectionResetter(log, definition.getDriver()); setDefinition(definition); connectionValidator = new DefaultConnectionValidator(); if (definition.getStatistics() != null) { try { admin = new Admin(definition); } catch (ProxoolException e) { log.error("Failed to initialise statistics", e); } } ShutdownHook.init(); } /** Starts up house keeping and prototyper threads. */ protected void start() throws ProxoolException { connectionPoolUp = true; prototyper = new Prototyper(this); HouseKeeperController.register(this); } /** * Get a connection from the pool. If none are available or there was an Exception * then an exception is thrown and something written to the log */ protected Connection getConnection() throws SQLException { //qb-rm StringBuilder sb = new StringBuilder(); sb.append(Thread.currentThread().toString()); if(RmConfig.getSingleton().isSystemDebugMode()) { String callStack = Arrays.deepToString(Thread.currentThread().getStackTrace()); if(callStack.length() > 10000) { callStack = callStack.substring(0, 10000); sb.append("[stack string length:" + callStack.length() + "]"); } callStack = callStack.replaceAll(", ", ",<br/>\n"); sb.append("<div nowrap>"); sb.append("<input type=\"button\" value=\"call stack\" onclick=\"if(this.parentNode.children[1].style.display=='block'){this.parentNode.children[1].style.display='none'}else{this.parentNode.children[1].style.display='block'}\">"); sb.append("<span style=\"display:none\">"); sb.append(callStack); sb.append("</span>"); sb.append("</div>"); } String requester = sb.toString(); /* *If we're busy, we need to return as quickly as possible. Because this is unsynchronized * we run the risk of refusing a connection when we might actually be able to. But that will * only happen when we're right at or near maximum connections anyway. */ try { prototyper.quickRefuse(); } catch (SQLException e) { connectionsRefusedCount++; if (admin != null) { admin.connectionRefused(); } log.info(displayStatistics() + " - " + MSG_MAX_CONNECTION_COUNT); timeOfLastRefusal = System.currentTimeMillis(); setUpState(StateListenerIF.STATE_OVERLOADED); throw e; } prototyper.checkSimultaneousBuildThrottle(); ProxyConnection proxyConnection = null; try { // We need to look at all the connections, but we don't want to keep looping round forever for (int connectionsTried = 0; connectionsTried < proxyConnections.size(); connectionsTried++) { // By doing this in a try/catch we avoid needing to synch on the size(). We need to do be // able to cope with connections being removed whilst we are going round this loop try { proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection); } catch (ArrayIndexOutOfBoundsException e) { // This is thrown by a Vector (which we no longer use), but is // kept here for a while. nextAvailableConnection = 0; proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection); } catch (IndexOutOfBoundsException e) { // This is thrown by a true List nextAvailableConnection = 0; proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection); } // setActive() returns false if the ProxyConnection wasn't available. You // can't set it active twice (at least, not without making it available again // in between) if (proxyConnection != null && proxyConnection.setStatus(ProxyConnectionIF.STATUS_AVAILABLE, ProxyConnectionIF.STATUS_ACTIVE)) { // Okay. So we have it. But is it working ok? if (getDefinition().isTestBeforeUse()) { if (!testConnection(proxyConnection)) { // Oops. No it's not. Let's choose another. proxyConnection = null; } } if (proxyConnection != null) { nextAvailableConnection++; break; } } else { proxyConnection = null; } nextAvailableConnection++; } // Did we get one? if (proxyConnection == null) { try { // No! Let's see if we can create one proxyConnection = prototyper.buildConnection(ProxyConnection.STATUS_ACTIVE, "on demand"); // Okay. So we have it. But is it working ok? if (getDefinition().isTestBeforeUse()) { if (!testConnection(proxyConnection)) { // Oops. No it's not. There's not much more we can do for now throw new SQLException("Created a new connection but it failed its test"); } } } catch (SQLException e) { throw e; } catch (ProxoolException e) { log.debug("Couldn't get connection", e); throw new SQLException(e.toString()); } catch (Throwable e) { log.error("Couldn't get connection", e); throw new SQLException(e.toString()); } } } catch (SQLException e) { throw e; } catch (Throwable t) { log.error("Problem getting connection", t); throw new SQLException(t.toString()); } finally { if (proxyConnection != null) { connectionsServedCount++; proxyConnection.setRequester(requester); } else { connectionsRefusedCount++; if (admin != null) { admin.connectionRefused(); } timeOfLastRefusal = System.currentTimeMillis(); setUpState(StateListenerIF.STATE_OVERLOADED); } } if (proxyConnection == null) { throw new SQLException("Unknown reason for not getting connection. Sorry."); } if (log.isDebugEnabled() && getDefinition().isVerbose()) { log.debug(displayStatistics() + " - Connection #" + proxyConnection.getId() + " served"); } // This gives the proxy connection a chance to reset itself before it is served. proxyConnection.open(); return ProxyFactory.getWrappedConnection(proxyConnection); } /** * Test the connection (if required) * If the connection fails the test, it is removed from the pool. * If no ConnectionValidatorIF is defined, then the test always succeed. * * @param proxyConnection the connection to test * @return TRUE if the connection pass the test, FALSE if it fails */ private boolean testConnection(ProxyConnectionIF proxyConnection) { // is validation enabled ? if( connectionValidator == null ) { return true; } // validate the connection boolean success = connectionValidator.validate(getDefinition(), proxyConnection.getConnection()); if( success ) { if (LOG.isDebugEnabled()) { LOG.debug(displayStatistics() + " - Connection #" + proxyConnection.getId() + " tested: OK"); } } else { proxyConnection.setStatus(ProxyConnectionIF.STATUS_NULL); removeProxyConnection(proxyConnection, ConnectionListenerIF.VALIDATION_FAIL, "it didn't pass the validation", ConnectionPool.REQUEST_EXPIRY, true); } // return return success; } /** * Add a ProxyConnection to the pool * @param proxyConnection new connection * @return true if the connection was added or false if it wasn't (for instance, if the definition it * was built with is out of date). */ protected boolean addProxyConnection(ProxyConnectionIF proxyConnection) { boolean added = false; try { acquireConnectionStatusWriteLock(); if (proxyConnection.getDefinition() == getDefinition()) { proxyConnections.add(proxyConnection); connectionCountByState[proxyConnection.getStatus()]++; added = true; } } finally { releaseConnectionStatusWriteLock(); } return added; } protected static String getStatusDescription(int status) { try { return STATUS_DESCRIPTIONS[status]; } catch (ArrayIndexOutOfBoundsException e) { return "Unknown status: " + status; } } /** * When you have finished with a Connection you should put it back here. That will make it available to others. * Unless it's due for expiry, in which case it will... expire */ protected void putConnection(ProxyConnectionIF proxyConnection) { if (admin != null) { long now = System.currentTimeMillis(); long start = proxyConnection.getTimeLastStartActive(); if (now - start < 0) { log.warn("Future start time detected. #" + proxyConnection.getId() + " start = " + new Date(start) + " (" + (now - start) + " milliseconds)"); } else if (now - start > 1000000) { log.warn("Suspiciously long active time. #" + proxyConnection.getId() + " start = " + new Date(start)); } admin.connectionReturned(now - start); } // It's possible that this connection is due for expiry if (proxyConnection.isMarkedForExpiry()) { if (proxyConnection.setStatus(ProxyConnectionIF.STATUS_ACTIVE, ProxyConnectionIF.STATUS_NULL)) { expireProxyConnection(proxyConnection, proxyConnection.getReasonCode(), proxyConnection.getReasonForMark(), REQUEST_EXPIRY); } } else { // Optionally, test it to see if it is ok if (getDefinition().isTestAfterUse()) { // It will get removed by this call if it is no good testConnection(proxyConnection); } // Let's make it available for someone else if (!proxyConnection.setStatus(ProxyConnectionIF.STATUS_ACTIVE, ProxyConnectionIF.STATUS_AVAILABLE)) { if (proxyConnection.getStatus() == ProxyConnectionIF.STATUS_AVAILABLE) { // This is *probably* because the connection has been closed twice. // Although we can't tell for sure. We'll have to refactor this to use // throw away wrappers to avoid this problem. log.warn("Unable to close connection " + proxyConnection.getId() + " - I suspect that it has been closed already. Closing it more" + " than once is unwise and should be avoided."); } else { log.warn("Unable to set status of connection " + proxyConnection.getId() + " from " + getStatusDescription(ProxyConnectionIF.STATUS_ACTIVE) + " to " + getStatusDescription(ProxyConnectionIF.STATUS_AVAILABLE) + " because it's state was " + getStatusDescription(proxyConnection.getStatus())); } } } if (log.isDebugEnabled() && getDefinition().isVerbose()) { log.debug(displayStatistics() + " - Connection #" + proxyConnection.getId() + " returned (now " + getStatusDescription(proxyConnection.getStatus()) + ")"); } } /** This means that there's something wrong the connection and it's probably best if no one uses it again. */ protected void throwConnection(ProxyConnectionIF proxyConnection, int reasonCode, String reason) { expireConnectionAsSoonAsPossible(proxyConnection, reasonCode, reason, true); } /** Get a ProxyConnection by index */ private ProxyConnectionIF getProxyConnection(int i) { return (ProxyConnectionIF) proxyConnections.get(i); } /** * Return an array of all the connections * @return array of connections */ protected ProxyConnectionIF[] getProxyConnections() { return (ProxyConnectionIF[]) proxyConnections.toArray(new ProxyConnectionIF[proxyConnections.size()]); } /** * Remove a ProxyConnection by calling its {@link ConnectionListenerIF#onDeath onDeath} event, * closing it (for real) and then removing it from the list. * @param proxyConnection the connection to remove * @param reason for log audit * @param forceExpiry true means close now, whether it is active or not; false means if it is active then * merely mark it for expiry so that it is removed as soon as it finished being active * @param triggerSweep if true then this removal will trigger a prototype sweep */ protected void removeProxyConnection(ProxyConnectionIF proxyConnection, int reasonCode, String reason, boolean forceExpiry, boolean triggerSweep) { // Just check that it is null if (forceExpiry || proxyConnection.isNull()) { proxyConnection.setStatus(ProxyConnectionIF.STATUS_NULL); /* Run some code everytime we destroy a connection */ try { onDeath(proxyConnection.getConnection(), reasonCode); } catch (SQLException e) { log.error("Problem during onDeath (ignored)", e); } // The reallyClose() method also decrements the connectionCount. try { proxyConnection.reallyClose(); } catch (SQLException e) { log.error(e); } try { // If we're shutting down then getting a write lock will cause a deadlock if (isConnectionPoolUp()) { acquireConnectionStatusWriteLock(); } proxyConnections.remove(proxyConnection); } finally { if (isConnectionPoolUp()) { releaseConnectionStatusWriteLock(); } } if (log.isDebugEnabled()) { log.debug(displayStatistics() + " - #" + FormatHelper.formatMediumNumber(proxyConnection.getId()) + " removed because " + reason + "."); } if (triggerSweep) { PrototyperController.triggerSweep(getDefinition().getAlias()); } } else { log.error(displayStatistics() + " - #" + FormatHelper.formatMediumNumber(proxyConnection.getId()) + " was not removed because isNull() was false."); } } protected void expireProxyConnection(ProxyConnectionIF proxyConnection, int reasonCode, String reason, boolean forceExpiry) { removeProxyConnection(proxyConnection, reasonCode, reason, forceExpiry, true); } /** * Call this to shutdown gracefully. * @param delay how long to wait for connections to become free before forcing them to close anyway */ protected void shutdown(int delay, String finalizerName) throws Throwable { final String alias = getDefinition().getAlias(); try { /* This will stop us giving out any more connections and may cause some of the threads to die. */ acquirePrimaryWriteLock(); if (connectionPoolUp) { connectionPoolUp = false; long startFinalize = System.currentTimeMillis(); shutdownThread = Thread.currentThread(); if (delay > 0) { log.info("Shutting down '" + alias + "' pool started at " + dateStarted + " - waiting for " + delay + " milliseconds for everything to stop. [ " + finalizerName + "]"); } else { log.info("Shutting down '" + alias + "' pool immediately [" + finalizerName + "]"); } /* Interrupt the threads (in case they're sleeping) */ boolean connectionClosedManually = false; try { try { HouseKeeperController.cancel(alias); } catch (ProxoolException e) { log.error("Shutdown couldn't cancel house keeper", e); } // Cancel the admin thread (for statistics) if (admin != null) { admin.cancelAll(); } /* Patience, patience. */ if (connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE] != 0) { long endWait = startFinalize + delay; LOG.info("Waiting until " + new Date(endWait) + " for all connections to become inactive (active count is " + connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE] + ")."); while (true) { long timeout = endWait - System.currentTimeMillis(); if (timeout > 0) { synchronized (Thread.currentThread()) { try { Thread.currentThread().wait(timeout); } catch (InterruptedException e) { log.debug("Interrupted whilst sleeping."); } } } int activeCount = connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE]; if (activeCount == 0) { break; } if (System.currentTimeMillis() < endWait) { LOG.info("Still waiting for active count to reach zero (currently " + activeCount + ")."); } else { // There are still connections active. Oh well, we're not _that_ patient LOG.warn("Shutdown waited for " + (System.currentTimeMillis() - startFinalize) + " milliseconds for all " + "the connections to become inactive but the active count is still " + activeCount + ". Shutting down anyway."); break; } Thread.sleep(100); } } prototyper.cancel(); // Silently close all connections for (int i = proxyConnections.size() - 1; i >= 0; i--) { long id = getProxyConnection(i).getId(); try { connectionClosedManually = true; removeProxyConnection(getProxyConnection(i), ConnectionListenerIF.SHUTDOWN, "of shutdown", true, false); if (log.isDebugEnabled()) { log.debug("Connection #" + id + " closed"); } } catch (Throwable t) { if (log.isDebugEnabled()) { log.debug("Problem closing connection #" + id, t); } } } } catch (Throwable t) { log.error("Unknown problem finalizing pool", t); } finally { ConnectionPoolManager.getInstance().removeConnectionPool(alias); if (log.isDebugEnabled()) { log.info("'" + alias + "' pool has been closed down by " + finalizerName + " in " + (System.currentTimeMillis() - startFinalize) + " milliseconds."); if (!connectionClosedManually) { log.debug("No connections required manual removal."); } } super.finalize(); } } else { if (log.isDebugEnabled()) { log.debug("Ignoring duplicate attempt to shutdown '" + alias + "' pool by " + finalizerName); } } } catch (Throwable t) { log.error(finalizerName + " couldn't shutdown pool", t); } finally { releasePrimaryWriteLock(); } } /** * You should {@link #acquireConnectionStatusReadLock acquire} * a read lock if you want this to be accurate (but that might have * an impact on the performance of your pool). * @see ConnectionPoolStatisticsIF#getAvailableConnectionCount */ public int getAvailableConnectionCount() { return connectionCountByState[ConnectionInfoIF.STATUS_AVAILABLE]; } /** * You should {@link #acquireConnectionStatusReadLock acquire} * a read lock if you want this to be accurate (but that might have * an impact on the performance of your pool). * @see ConnectionPoolStatisticsIF#getActiveConnectionCount */ public int getActiveConnectionCount() { return connectionCountByState[ConnectionInfoIF.STATUS_ACTIVE]; } /** * You should {@link #acquireConnectionStatusReadLock acquire} * a read lock if you want this to be accurate (but that might have * an impact on the performance of your pool). * @see ConnectionPoolStatisticsIF#getOfflineConnectionCount */ public int getOfflineConnectionCount() { return connectionCountByState[ConnectionInfoIF.STATUS_OFFLINE]; } protected String displayStatistics() { if (!loggedLegend) { log.info("Proxool statistics legend: \"s - r (a/t/o)\" > s=served, r=refused (only shown if non-zero), a=active, t=total, o=offline (being tested)"); loggedLegend = true; } StringBuffer statistics = new StringBuffer(); statistics.append(FormatHelper.formatBigNumber(getConnectionsServedCount())); if (getConnectionsRefusedCount() > 0) { statistics.append(" -"); statistics.append(FormatHelper.formatBigNumber(getConnectionsRefusedCount())); } statistics.append(" ("); statistics.append(FormatHelper.formatSmallNumber(getActiveConnectionCount())); statistics.append("/"); statistics.append(FormatHelper.formatSmallNumber(getAvailableConnectionCount() + getActiveConnectionCount())); statistics.append("/"); statistics.append(FormatHelper.formatSmallNumber(getOfflineConnectionCount())); statistics.append(")"); // Don't need this triple check any more. /* if (getDefinition().getDebugLevel() == ConnectionPoolDefinitionIF.DEBUG_LEVEL_LOUD) { statistics.append(", cc="); statistics.append(connectionCount); statistics.append(", ccc="); statistics.append(connectedConnectionCount); } */ return statistics.toString(); } protected void expireAllConnections(int reasonCode, String reason, boolean merciful) { // Do this in two stages because expiring a connection will trigger // the prototyper to make more. And that might mean we end up // killing a newly made connection; Set pcs = new HashSet(); for (int i = proxyConnections.size() - 1; i >= 0; i--) { pcs.add(proxyConnections.get(i)); } Iterator i = pcs.iterator(); while (i.hasNext()) { ProxyConnectionIF pc = (ProxyConnectionIF) i.next(); expireConnectionAsSoonAsPossible(pc, reasonCode, reason, merciful); } } protected void expireConnectionAsSoonAsPossible(ProxyConnectionIF proxyConnection, int reasonCode, String reason, boolean merciful) { if (proxyConnection.setStatus(ProxyConnectionIF.STATUS_AVAILABLE, ProxyConnectionIF.STATUS_OFFLINE)) { if (proxyConnection.setStatus(ProxyConnectionIF.STATUS_OFFLINE, ProxyConnectionIF.STATUS_NULL)) { // It is. Expire it now . expireProxyConnection(proxyConnection, reasonCode, reason, REQUEST_EXPIRY); } } else { // Oh no, it's in use. if (merciful) { //Never mind, we'll mark it for expiry // next time it is available. This will happen in the // putConnection() method. proxyConnection.markForExpiry(reason); if (log.isDebugEnabled()) { log.debug(displayStatistics() + " - #" + FormatHelper.formatMediumNumber(proxyConnection.getId()) + " marked for expiry."); } } else { // So? Kill, kill, kill // We have to make sure it's null first. expireProxyConnection(proxyConnection, reasonCode, reason, FORCE_EXPIRY); } } // END if (proxyConnection.setOffline()) } protected void registerRemovedConnection(int status) { prototyper.connectionRemoved(); connectionCountByState[status]--; } /** * You should {@link #acquireConnectionStatusWriteLock acquire} a write lock * before calling this method * @param oldStatus so we know which count to decrement * @param newStatus so we know which count to increment */ protected void changeStatus(int oldStatus, int newStatus) { // LOG.debug("About to change status"); connectionCountByState[oldStatus]--; connectionCountByState[newStatus]++; // LOG.debug("Changing status from " + oldStatus + " to " + newStatus); // Check to see if shutdown is waiting for all connections to become // non-active if (shutdownThread != null && connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE] == 0) { synchronized (shutdownThread) { shutdownThread.notify(); } } } public long getConnectionsServedCount() { return connectionsServedCount; } public long getConnectionsRefusedCount() { return connectionsRefusedCount; } protected ConnectionPoolDefinition getDefinition() { return definition; } /** * Changes both the way that any new connections will be made, and the behaviour of the pool. Consider * calling expireAllConnections() if you're in a hurry. */ protected synchronized void setDefinition(ConnectionPoolDefinition definition) throws ProxoolException { this.definition = definition; try { Class.forName(definition.getDriver()); } catch (ClassNotFoundException e) { log.error("Couldn't load class " + definition.getDriver(), e); throw new ProxoolException("Couldn't load class " + definition.getDriver()); } catch (NullPointerException e) { log.error("Definition did not contain driver", e); throw new ProxoolException("Definition did not contain driver"); } } /** * @deprecated use {@link #addStateListener(StateListenerIF)} instead. */ public void setStateListener(StateListenerIF stateListener) { addStateListener(stateListener); } public void addStateListener(StateListenerIF stateListener) { this.compositeStateListener.addListener(stateListener); } public boolean removeStateListener(StateListenerIF stateListener) { return this.compositeStateListener.removeListener(stateListener); } /** * @deprecated use {@link #addConnectionListener(ConnectionListenerIF)} instead. */ public void setConnectionListener(ConnectionListenerIF connectionListener) { addConnectionListener(connectionListener); } public void addConnectionListener(ConnectionListenerIF connectionListener) { this.compositeConnectionListener.addListener(connectionListener); } public boolean removeConnectionListener(ConnectionListenerIF connectionListener) { return this.compositeConnectionListener.removeListener(connectionListener); } /** Call the onBirth() method on each StateListenerIF . */ protected void onBirth(Connection connection) throws SQLException { this.compositeConnectionListener.onBirth(connection); } /** Call the onDeath() method on each StateListenerIF . */ protected void onDeath(Connection connection, int reasonCode) throws SQLException { this.compositeConnectionListener.onDeath(connection, reasonCode); } /** Call the onExecute() method on each StateListenerIF . */ protected void onExecute(String command, long elapsedTime, Exception exception) throws SQLException { if (exception == null) { this.compositeConnectionListener.onExecute(command, elapsedTime); } else { this.compositeConnectionListener.onFail(command, exception); } } /** * Is there a {@link ConnectionListenerIF listener} for connections * @return true if there is a listener registered. */ protected boolean isConnectionListenedTo() { return !compositeConnectionListener.isEmpty(); } public String toString() { return getDefinition().toString(); } public int getUpState() { return upState; } public void setUpState(int upState) { if (this.upState != upState) { compositeStateListener.upStateChanged(upState); this.upState = upState; } } protected Collection getConnectionInfos() { Collection cis = null; cis = new TreeSet(); Iterator i = proxyConnections.iterator(); while (i.hasNext()) { ConnectionInfoIF connectionInfo = (ConnectionInfoIF) i.next(); ConnectionInfo ci = new ConnectionInfo(); ci.setAge(connectionInfo.getAge()); ci.setBirthDate(connectionInfo.getBirthDate()); ci.setId(connectionInfo.getId()); ci.setMark(connectionInfo.getMark()); ci.setRequester(connectionInfo.getRequester()); ci.setStatus(connectionInfo.getStatus()); ci.setTimeLastStartActive(connectionInfo.getTimeLastStartActive()); ci.setTimeLastStopActive(connectionInfo.getTimeLastStopActive()); ci.setDelegateUrl(connectionInfo.getDelegateUrl()); ci.setProxyHashcode(connectionInfo.getProxyHashcode()); ci.setDelegateHashcode(connectionInfo.getDelegateHashcode()); String[] sqlCalls = connectionInfo.getSqlCalls(); for (int j = 0; j < sqlCalls.length; j++) { ci.addSqlCall(sqlCalls[j]); } cis.add(ci); } return cis; } /** * Manually expire a connection. * @param id the id of the connection to kill * @param forceExpiry use true to expire even if it is in use * @return true if the connection was found and expired, else false */ public boolean expireConnection(long id, boolean forceExpiry) { boolean success = false; ProxyConnection proxyConnection = null; // We need to look at all the connections, but we don't want to keep looping round forever for (int connectionsTried = 0; connectionsTried < proxyConnections.size(); connectionsTried++) { // By doing this in a try/catch we avoid needing to synch on the size(). We need to do be // able to cope with connections being removed whilst we are going round this loop try { proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection); } catch (IndexOutOfBoundsException e) { nextAvailableConnection = 0; proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection); } if (proxyConnection.getId() == id) { // This is the one proxyConnection.setStatus(ProxyConnectionIF.STATUS_AVAILABLE, ProxyConnectionIF.STATUS_OFFLINE); proxyConnection.setStatus(ProxyConnectionIF.STATUS_OFFLINE, ProxyConnectionIF.STATUS_NULL); removeProxyConnection(proxyConnection, ConnectionListenerIF.MANUAL_EXPIRY, "it was manually killed", forceExpiry, true); success = true; break; } nextAvailableConnection++; } if (!success) { if (log.isDebugEnabled()) { log.debug(displayStatistics() + " - couldn't find " + FormatHelper.formatMediumNumber(proxyConnection.getId()) + " and I've just been asked to expire it"); } } return success; } public Log getLog() { return log; } /** * {@link ConnectionResetter#initialise Initialises} the ConnectionResetter. * @param connection sample Connection to use for default values */ protected void initialiseConnectionResetter(Connection connection) { connectionResetter.initialise(connection); } /** * {@link ConnectionResetter#reset Resets} a Connection to its * original state. * @param connection the one to reset * @param id the id of the connection * @throws SQLException if the call to {@link java.sql.Connection#isClosed()} fails * @return true if it was successfully reset, false if there was a problem (like the connection being already closed) */ protected boolean resetConnection(Connection connection, String id) throws SQLException { if (connection.isClosed()) { return false; } else { return connectionResetter.reset(connection, id); } } /** * @see ConnectionPoolStatisticsIF#getDateStarted */ public Date getDateStarted() { return dateStarted; } /** * Get the admin for this pool * @return admin */ protected Admin getAdmin() { return admin; } protected boolean isLocked() { return locked; } protected void lock() { locked = true; } protected void unlock() { locked = false; } /** * Call this if you want to do something important to the pool. Like shut it down. * @throws InterruptedException if we couldn't */ protected void acquirePrimaryReadLock() throws InterruptedException { // if (log.isDebugEnabled()) { // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // log.debug("About to acquire primary read lock", e); // } // // log.debug("About to acquire primary read lock"); // } primaryReadWriteLock.readLock().acquire(); // if (log.isDebugEnabled()) { // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // log.debug("Acquired primary read lock", e); // } // //log.debug("Acquired primary read lock"); // } } /** * @see #acquirePrimaryReadLock */ protected void releasePrimaryReadLock() { // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // log.debug("Released primary read lock", e); // } //log.debug("Released primary read lock"); primaryReadWriteLock.readLock().release(); } /** * Call this everytime you build a connection. It ensures that we're not * trying to shutdown the pool whilst we are building a connection. So you * should check that the pool is still {@link #isConnectionPoolUp up}. * @throws InterruptedException if there was a problem. */ protected void acquirePrimaryWriteLock() throws InterruptedException { // boolean success = false; // try { // if (log.isDebugEnabled()) { // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // log.debug("About to acquire primary write lock", e); // } // //log.debug("About to acquire primary write lock"); // } primaryReadWriteLock.writeLock().acquire(); // success = true; // if (log.isDebugEnabled()) { // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // log.debug("Acquired primary write lock", e); // } // //log.debug("Acquired primary write lock"); // } // } finally { // if (log.isDebugEnabled() && !success) { // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // log.debug("Failed to acquire primary write lock", e); // } // //log.debug("Failed to acquire primary write lock"); // } // } } /** * @see #acquirePrimaryReadLock */ protected void releasePrimaryWriteLock() { primaryReadWriteLock.writeLock().release(); // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // log.debug("Released primary write lock", e); // } //log.debug("Released primary write lock"); } /** * Is the pool up? * @return false is the connection pool has been {@link #shutdown shutdown} * (or is in the process of being shutdown). */ protected boolean isConnectionPoolUp() { return connectionPoolUp; } protected static final boolean FORCE_EXPIRY = true; protected static final boolean REQUEST_EXPIRY = false; /** * The time (in milliseconds) that we last refused a connection * @return timeOfLastRefusal */ protected long getTimeOfLastRefusal() { return timeOfLastRefusal; } protected void acquireConnectionStatusWriteLock() { try { // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // LOG.debug("About to acquire connectionStatus write lock", e); // } connectionStatusReadWriteLock.writeLock().acquire(); // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // LOG.debug("Acquired connectionStatus write lock", e); // } } catch (InterruptedException e) { log.error("Couldn't acquire connectionStatus write lock", e); } } protected void releaseConnectionStatusWriteLock() { connectionStatusReadWriteLock.writeLock().release(); // try { // throw new RuntimeException("TRACE ONLY"); // } catch (RuntimeException e) { // LOG.debug("Released connectionStatus write lock", e); // } } protected void acquireConnectionStatusReadLock() { try { connectionStatusReadWriteLock.readLock().acquire(); } catch (InterruptedException e) { log.error("Couldn't acquire connectionStatus read lock", e); } } protected boolean attemptConnectionStatusReadLock(long msecs) { try { return connectionStatusReadWriteLock.readLock().attempt(msecs); } catch (InterruptedException e) { log.error("Couldn't acquire connectionStatus read lock", e); return false; } } protected void releaseConnectionStatusReadLock() { connectionStatusReadWriteLock.readLock().release(); // LOG.debug("Released connectionStatus read lock"); } protected Prototyper getPrototyper() { return prototyper; } public long getConnectionCount() { return getPrototyper().getConnectionCount(); } } /* Revision history: $Log: ConnectionPool.java,v $ Revision 1.86 2007/01/25 23:38:24 billhorsman Scrapped onAboutToDie and altered onDeath signature instead. Now includes reasonCode (see ConnectionListenerIF) Revision 1.85 2007/01/25 00:10:24 billhorsman New onAboutToDie event for ConnectionListenerIF that gets called if the maximum-active-time is exceeded. Revision 1.84 2006/03/23 11:44:57 billhorsman More information when quickly refusing Revision 1.83 2006/01/18 14:40:01 billhorsman Unbundled Jakarta's Commons Logging. Revision 1.82 2005/10/07 08:19:05 billhorsman New sqlCalls gives list of SQL calls rather than just he most recent (for when a connection makes more than one call before being returned to the pool) Revision 1.81 2005/10/02 12:32:02 billhorsman Make connectionCount available to statistics Revision 1.80 2005/09/26 09:54:14 billhorsman Avoid suspected deadlock when getting a detailed snapshot. Only attempt to get the concurrent lock for 10 seconds before giving up. Revision 1.79 2005/05/04 16:26:31 billhorsman Only add a new connection if the definition matches Revision 1.78 2004/03/25 22:02:15 brenuart First step towards pluggable ConnectionBuilderIF & ConnectionValidatorIF. Include some minor refactoring that lead to deprecation of some PrototyperController methods. Revision 1.76 2004/02/23 17:47:32 billhorsman Improved message that gets logged if the state change of a connection fails. Revision 1.75 2004/02/23 17:38:58 billhorsman Improved message that gets logged if you close a connection more than once. Revision 1.74 2004/02/12 13:02:17 billhorsman Catch correct exception when iterating through list. Revision 1.73 2003/12/09 18:54:55 billhorsman Make closure of statement during connection test more robust - credit to John Hume Revision 1.72 2003/11/04 13:52:01 billhorsman Fixed warning message Revision 1.71 2003/10/30 00:11:15 billhorsman Debug info and error logged if unsuccessful attempt to put connection back in pool. Plus connectioninfo comparator changed Revision 1.70 2003/09/30 18:39:08 billhorsman New test-before-use, test-after-use and fatal-sql-exception-wrapper-class properties. Revision 1.69 2003/09/30 07:50:04 billhorsman Smarter throwing of caught SQLExceptions without wrapping them up inside another (and losing the stack trace) Revision 1.68 2003/09/20 17:04:06 billhorsman Fix for incorrect OFFLINE count when house keeper removed a connection if the test SQL failed. This meant that the offline count went negative. The only consequence of that is that the logs look funny. Revision 1.67 2003/08/30 14:54:04 billhorsman Checkstyle Revision 1.66 2003/04/10 08:23:54 billhorsman removed very frequent debug Revision 1.65 2003/03/11 23:58:04 billhorsman fixed deadlock on connection expiry Revision 1.64 2003/03/11 14:51:49 billhorsman more concurrency fixes relating to snapshots Revision 1.63 2003/03/11 01:16:29 billhorsman removed misleasing debug Revision 1.62 2003/03/11 00:32:13 billhorsman fixed negative timeout Revision 1.61 2003/03/10 23:39:51 billhorsman shutdown is now notified when active count reaches zero Revision 1.60 2003/03/10 16:26:35 billhorsman removed debug traces Revision 1.59 2003/03/10 15:26:44 billhorsman refactoringn of concurrency stuff (and some import optimisation) Revision 1.58 2003/03/05 18:42:32 billhorsman big refactor of prototyping and house keeping to drastically reduce the number of threads when using many pools Revision 1.57 2003/03/03 16:06:44 billhorsman name house keeper and prototyper threads now includes alias Revision 1.56 2003/03/03 11:11:57 billhorsman fixed licence Revision 1.55 2003/02/28 18:08:55 billhorsman OVERLOAD state is now triggered immediately rather than waiting for house keeper Revision 1.54 2003/02/28 10:10:25 billhorsman on death now gets called for connections killed during shutdown Revision 1.53 2003/02/26 16:05:52 billhorsman widespread changes caused by refactoring the way we update and redefine pool definitions. Revision 1.52 2003/02/26 12:57:30 billhorsman added TO DO Revision 1.51 2003/02/19 23:46:10 billhorsman renamed monitor package to admin Revision 1.50 2003/02/19 23:07:46 billhorsman state changes are now only calculated every time the house keeper runs, but it's more accurate Revision 1.49 2003/02/19 22:38:33 billhorsman fatal sql exception causes house keeper to run immediately Revision 1.48 2003/02/18 16:49:59 chr32 Added possibility to remove connection and state listeners. Revision 1.47 2003/02/12 12:27:16 billhorsman log proxy hashcode too Revision 1.46 2003/02/07 17:26:04 billhorsman deprecated removeAllConnectionPools in favour of shutdown (and dropped unreliable finalize() method) Revision 1.45 2003/02/07 14:19:01 billhorsman fixed deprecated use of debugLevel property Revision 1.44 2003/02/07 14:16:46 billhorsman support for StatisticsListenerIF Revision 1.43 2003/02/07 10:27:47 billhorsman change in shutdown procedure to allow re-registration Revision 1.42 2003/02/07 01:48:15 chr32 Started using new composite listeners. Revision 1.41 2003/02/06 17:41:04 billhorsman now uses imported logging Revision 1.40 2003/02/04 17:18:30 billhorsman move ShutdownHook init code Revision 1.39 2003/02/04 15:59:50 billhorsman finalize now shuts down StatsRoller timer Revision 1.38 2003/02/02 23:35:48 billhorsman removed ReloadMonitor to remove use of System properties Revision 1.37 2003/01/31 16:53:16 billhorsman checkstyle Revision 1.36 2003/01/31 11:49:28 billhorsman use Admin instead of Stats Revision 1.35 2003/01/31 00:20:05 billhorsman statistics is now a string to allow multiple, comma-delimited values (plus better logging of errors during destruction) Revision 1.34 2003/01/30 17:22:21 billhorsman add statistics support Revision 1.33 2003/01/27 18:26:35 billhorsman refactoring of ProxyConnection and ProxyStatement to make it easier to write JDK 1.2 patch Revision 1.32 2003/01/15 14:51:40 billhorsman checkstyle Revision 1.31 2003/01/15 12:01:37 billhorsman added getDateStarted() Revision 1.30 2003/01/15 00:07:43 billhorsman now uses FastArrayList instead of Vector for thread safe improvements Revision 1.29 2002/12/18 12:16:22 billhorsman double checking of connection state counts Revision 1.28 2002/12/17 17:15:39 billhorsman Better synchronization of status stuff Revision 1.27 2002/12/17 16:52:51 billhorsman synchronize part of removeProxyConnection to avoid possible bug where connection by status count drifts. Revision 1.26 2002/12/12 12:28:34 billhorsman just in case: changed == 0 to < 1 Revision 1.25 2002/12/12 10:48:47 billhorsman checkstyle Revision 1.24 2002/12/03 12:24:00 billhorsman fixed fatal sql exception Revision 1.23 2002/11/12 20:24:12 billhorsman checkstyle Revision 1.22 2002/11/12 20:18:23 billhorsman Made connection resetter a bit more friendly. Now, if it encounters any problems during reset then that connection is thrown away. This is going to cause you problems if you always close connections in an unstable state (e.g. with transactions open. But then again, it's better to know about that as soon as possible, right? Revision 1.21 2002/11/09 15:48:55 billhorsman new isConnectionListenedTo() to stop unnecessary processing if nobody is listening Revision 1.20 2002/11/08 18:03:50 billhorsman when connections are closed because they have been active for too long then a log message is written of level WARN, and it includes the name of the thread responsible (reminder, name your threads). Revision 1.19 2002/11/07 19:31:25 billhorsman added sanity check against suspected situation where you can make more connections than the maximumConnectionCount Revision 1.18 2002/11/07 19:17:55 billhorsman removed obsolete method Revision 1.17 2002/11/06 20:27:30 billhorsman supports the ConnectionResetter Revision 1.16 2002/11/05 21:24:18 billhorsman cosmetic: changed format of statistics dumped to log to make it less confusing for locales that use a space separator for thousands Revision 1.15 2002/11/02 13:57:33 billhorsman checkstyle Revision 1.14 2002/10/30 21:19:17 billhorsman make use of ProxyFactory Revision 1.13 2002/10/29 23:00:33 billhorsman fixed sign error in prototyper (that meant that protoyping never happened) Revision 1.12 2002/10/29 22:58:22 billhorsman added connection hashcode to debug Revision 1.11 2002/10/28 19:44:03 billhorsman small change to cleanup log Revision 1.10 2002/10/27 13:02:45 billhorsman change to prototyper logic to make it clearer (but no functional change) Revision 1.9 2002/10/27 12:07:45 billhorsman fix bug where prototyper kept making connections up to maximum. Log now gives reason why connection was prototyped. Fix bug where definition with no properties was not allowed (it is now). Revision 1.8 2002/10/25 10:12:52 billhorsman Improvements and fixes to the way connection pools close down. Including new ReloadMonitor to detect when a class is reloaded. Much better logging too. Revision 1.7 2002/10/24 17:25:20 billhorsman cleaned up logging and made it more informative Revision 1.6 2002/10/23 21:04:36 billhorsman checkstyle fixes (reduced max line width and lenient naming convention Revision 1.5 2002/10/16 11:46:23 billhorsman removed obsolete cleanupClob method and made onBirth call failsafe Revision 1.4 2002/09/19 10:33:57 billhorsman added ProxyConnection#toString Revision 1.3 2002/09/18 13:48:56 billhorsman checkstyle and doc Revision 1.2 2002/09/13 12:13:50 billhorsman added debug and fixed ClassCastException during housekeeping Revision 1.1.1.1 2002/09/13 08:12:55 billhorsman new Revision 1.13 2002/08/24 19:57:15 billhorsman checkstyle changes Revision 1.12 2002/08/24 19:44:13 billhorsman fixes for logging Revision 1.11 2002/07/10 16:14:47 billhorsman widespread layout changes and move constants into ProxoolConstants Revision 1.10 2002/07/04 09:05:36 billhorsmaNn Fixes Revision 1.9 2002/07/02 11:19:08 billhorsman layout code and imports Revision 1.8 2002/07/02 11:14:26 billhorsman added test (andbug fixes) for FileLogger Revision 1.7 2002/07/02 08:39:55 billhorsman getConnectionInfos now returns a Collection instead of an array. displayStatistics is now available to ProxoolFacade. Prototyper no longer tries to make connections when maximum is reached (stopping unnecessary log messages). bug fix. Revision 1.6 2002/06/28 11:19:47 billhorsman improved doc */