All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.ibm.as400.access.JDConnectionPoolManager Maven / Gradle / Ivy

There is a newer version: 20.0.7
Show newest version
///////////////////////////////////////////////////////////////////////////////
//                                                                             
// JTOpen (IBM Toolbox for Java - OSS version)                                 
//                                                                             
// Filename: JDConnectionPoolManager.java
//                                                                             
// The source code contained herein is licensed under the IBM Public License   
// Version 1.0, which has been approved by the Open Source Initiative.         
// Copyright (C) 2005-2005 International Business Machines Corporation and     
// others. All rights reserved.                                                
//                                                                             
///////////////////////////////////////////////////////////////////////////////

package com.ibm.as400.access;

import java.sql.SQLException;
import javax.sql.ConnectionEventListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Stack;
import java.util.TreeSet;

/**
 This is the Toolbox's built-in connection pooling manager.
 Each instance of AS400JDBCManagedDataSource creates and uses an instance of this class to manage its pooled connections.

 

Note that the Toolbox's original (older) data source classes are not automatically managed:

  • AS400JDBCDataSource
  • AS400JDBCConnectionPoolDataSource
  • AS400JDBCXADataSource
**/ final class JDConnectionPoolManager implements ConnectionEventListener { static boolean DEBUG = false; // turn on if debugging static boolean GATHER_STATS = false; // turn on to gather/print additional statistics (useful when debugging and performance-tuning) static boolean TESTING_ERROR_EVENTS = false; // Terminology for connections: // 'active' == in-use == allocated // 'available' == not-in-use == unallocated // 'condemned' == neither of the above; selected for removal from pool // Note to maintenance developer: Be _very_ careful about synchronizing methods. They are likely to cause deadlocks. // All of the following lists (activeConnections_, availableConnections_, and condemnedConnections_) are ultimately just lists of AS400JDBCPooledConnection objects. They're just organized in different ways. // Note: When obtaining multiple simultaneous locks, the order of synchronization must never deviate from: active, available1, available2, condemned // Connections that are currently active (allocated). // This is a sorted list containing AS400JDBCPooledConnection objects. // Connections are added to this list when they are allocated to an application. // Connections are removed from this list when they are returned to the pool, // or when they are identified as "expired" by the reaper daemon. // The connections in this list are sorted by creation date (in ascending order). // Since the list is maintained in order of creation time, the reaper daemon can quickly // identify any "expired" connections, without having to examine the entire list. // The set contains AS400JDBCPooledConnection objects. // The Comparator compares connection creation dates; for identical creation dates, hashcodes are also compared. private final TreeSet activeConnections_ = new TreeSet(new JDAgeComparator()); // Companion list for the activeConnections_ list. // Active connections that have incurred connection errors, and therefore must not be reused. // This is a sorted list containing AS400JDBCPooledConnection objects. // Connections are added to this list when they fire a connectionErrorOccurred event. // Connections are removed from this list when they are returned to the pool, // or when they are identified as "expired" by the reaper daemon. // The set contains AS400JDBCPooledConnection objects. // The Comparator simply compares connection hashcodes. private final HashSet activeConnectionsInError_ = new HashSet(); // Connections that are available to be handed out. // Connections are added to this list when they are created by the pool manager, // and when they are returned to the pool after being allocated. // Connections are removed from this list when they are allocated to an application, // or when they are identified as "stale" by the maintainer daemon. // This array contains two HashMaps (plus a placeholder for swapping). // Each HashMap maps JDConnectionPoolKeys to Stacks. // Each Stack contains AS400JDBCPooledConnection objects. // The connectionKey gets you to the Stack that contains connections for that key. private final HashMap[] availableConnections_ = {new HashMap(100), new HashMap(100), null}; // Companion list for the availableConnections_ list. // Connections are added to and removed from this list, whenever they are added to / removed from // the availableConnections_ list. // This array contains two LinkedHashSets (plus a placeholder for swapping). // Each LinkedHashSet contains AS400JDBCPooledConnection objects, in the order in which they were (most recently) added to the associated availableConnections_ list. // Note: The availableConnections_ and idledConnectionsSequence_ lists must always be swapped simultaneously. The availableConnectionsIdledSequence_ list is "metadata" for the availableConnections_ list. // Access to an availableConnectionsIdledSequence_ list must only be done within a sync block for the associated availableConnections_ list. private final LinkedHashSet[] availableConnectionsIdledSequence_ = {new LinkedHashSet(), new LinkedHashSet(), null}; // Connections that are no longer in use, which are staged to be removed from the pool. // Connections are added to this list when it is determined that they should be physically closed. // Connections are removed from this list when they are physically closed. // This list contains AS400JDBCPooledConnection objects. private final ArrayList condemnedConnections_ = new ArrayList(100); // Connection keys that have been invalidated via the invalidate() method. // This list contains JDConnectionPoolKey objects. private final HashSet invalidatedKeys_ = new HashSet(); // The datasource being used for logging and tracing. private AS400JDBCManagedDataSource logger_; // The datasource that creates new connections, and specifies pool properties. private AS400JDBCManagedConnectionPoolDataSource cpds_; // Standard JDBC connection pool properties. (See JDBC Tutorial p. 442, table 14.1) // For consistency, all time-related properties in this class are in units of milliseconds. // Note that all "PoolSize_" variables refer to the total number of connections ('active', 'available', and 'condemned'), not just available connections. private int initialPoolSize_;// The # of physical connections the pool should contain // when it is created. private int minPoolSize_; // The minimum # of physical connections in the pool. private int maxPoolSize_; // The maximum # of physical connections the pool should contain. // 0 (zero) indicates no maximum size. private long maxIdleTime_; // The # of milliseconds that a physical connection should // remain unused in the pool before it is closed; that is, when the // connection is considered "stale". // 0 (zero) indicates no time limit. // See JDBC Tutorial, p. 643, 2nd paragraph. private long propertyCycle_; // The interval, in milliseconds, that the pool should wait before // enforcing the policy defined by the values currently assigned // to these connection pool properties. // maxStatements_; // The total # of statements the pool should keep open. // 0 (zero) indicates that the caching of statements is disabled. // Note: The Toolbox JDBC driver doesn't cache prepared statements. // Additional connection pool properties. private long maxLifetime_; // The # of milliseconds that a physical connection should // remain in the pool before it is closed; that is, when the // connection is considered "expired". // 0 (zero) indicates no time limit. private long maintainerInterval_; // Max # of milliseconds between maintainer daemon cycles. private long reaperInterval_; // Max # of milliseconds between reaper daemon cycles. private long scavengerInterval_; // Max # of milliseconds between scavenger daemon cycles. private int condemnedListLengthThreshold_; // When the 'condemned' list reaches this length, we wake up the scavenger daemon to close all connections on 'condemned' list. private long minSwapInterval_; // Min # of milliseconds between list swaps. // The total (cumulative) number of physical connections that have ever been created by this pool manager. private int numConnectionsCreated_; // The total (cumulative) number of physical connections that have ever been closed (and removed from the pool) by this pool manager. private int numConnectionsDestroyed_; // Note: At any given moment, the total number of connections in the pool is the difference of the above two values. private final Object connectionsCreatedLock_ = new Object(); private final Object connectionsDestroyedLock_ = new Object(); private boolean poolSizeLimited_; // Total pool size (number of open connections) is limited. private boolean enforceMaxPoolSize_; // Pool size limitation is enforced. private boolean connectionLifetimeLimited_; // Connections have maximum lifetime; i.e. they can expire. private boolean pretestConnections_; // Pretest connections before allocating them to requesters. private boolean reuseConnections_; // Re-use connections that have been returned to pool. private int minDefaultStackSize_; // Desired minimum size of 'available' stack for the default key. private JDPoolMaintainer poolMaintainer_; // Manages the 'available' lists. private Thread maintainerDaemon_; private JDPoolReaper poolReaper_; // Removes expired connections from 'active' list. private Thread reaperDaemon_; // Only started if connectionLifetimeLimited_ == true. private JDPoolScavenger poolScavenger_; // Closes physical connections in 'condemned' list. private Thread scavengerDaemon_; private boolean keepDaemonsAlive_ = true; // When this is false, the daemons shut down. private final Object maintainerSnoozeLock_ = new Object(); private final Object maintainerSleepLock_ = new Object(); private final Object reaperSleepLock_ = new Object(); private final Object scavengerSleepLock_ = new Object(); private boolean poolPaused_ = false; private final Object poolPauseLock_ = new Object(); private boolean fillingPool_; // gets set to 'true' while fillPool() is running private boolean poolClosed_; // gets set to 'true' (and stays true) when closePool() starts private boolean poolClosedCompletely_; // gets set to 'true' when closePool() completes private boolean needMoreConnections_; // informs the maintainerDaemon_ that the foreground 'available' list is running low on default-key connections, and needs to be swapped-out // Time at which the foreground and background 'available' lists were last compared (and possibly swapped). private long timeLastSwapAttempted_ = 0L; private boolean swapInProgress_; private final Object swapLock_ = new Object(); private boolean healthCheckInProgress_; private final Object healthCheckLock_ = new Object(); // 'Sides' of the availableConnections_ list: "foreground" list and "background" list. private static final int FOREGROUND = 0; // specifies list accessed by the foreground process private static final int BACKGROUND = 1; // specifies list updated by the daemons private static final int HOLD = 2; // dummy value; used when swapping the above 2 lists private static final int SYNC_NONE = 10; private static final int SYNC_ALL = 11; // Used for debugging only: private int maintainerDaemonCycles_ = 0; private int reaperDaemonCycles_ = 0; private int scavengerDaemonCycles_ = 0; private int numGetConnectionCalls_received_ = 0; private int numGetConnectionCalls_succeeded_ = 0; private int numGetConnectionCalls_returnedNull_ = 0; private int numGetConnectionCalls_whileClosing_ = 0; private int connectionsReturnedToPool_ = 0; private int connectionErrorsOccurred_ = 0; private int staleConnectionsIdentified_ = 0; private int expiredConnectionsIdentifiedByReaper_ = 0; private int expiredConnectionsIdentifiedWhenReturned_ = 0; private int surplusPrecreatedConnectionsRemoved_ = 0; private int survivingConnectionsRemoved_ = 0; private int condemnedConnectionsRemoved_ = 0; private int swapsAttempted_ = 0; private int swapsSucceeded_ = 0; private int swapsFailed_ = 0; private int swapsFailed_notWorthIt_ = 0; private int swapsSucceeded_foreground_ = 0; private int swapsSucceeded_background_ = 0; private int swapsFailed_foreground_ = 0; private int swapsFailed_background_ = 0; private int swapsFailed_premature_ = 0; private int swapsFailed_foreground_inProgress_ = 0; private int swapsFailed_background_inProgress_ = 0; private int swapsFailed_foreground_daemonAwake_ = 0; /** **/ JDConnectionPoolManager(AS400JDBCManagedDataSource logger, AS400JDBCManagedConnectionPoolDataSource cpds) { // We assume that the caller will never give us null args. logger_ = logger; cpds_ = cpds; initialPoolSize_ = cpds_.getInitialPoolSize(); minPoolSize_ = cpds_.getMinPoolSize(); maxPoolSize_ = cpds_.getMaxPoolSize(); enforceMaxPoolSize_ = cpds_.isEnforceMaxPoolSize(); pretestConnections_ = cpds_.isPretestConnections(); reuseConnections_ = cpds_.isReuseConnections(); maxIdleTime_ = cpds_.getMaxIdleTime()*1000; // convert to milliseconds maxLifetime_ = cpds_.getMaxLifetime()*1000; // convert to milliseconds propertyCycle_ = cpds_.getPropertyCycle()*1000; // convert to milliseconds if (DEBUG) { logInformation("initialPoolSize_: " + initialPoolSize_); logInformation("minPoolSize_: " + minPoolSize_); logInformation("maxPoolSize_: " + maxPoolSize_); logInformation("enforceMaxPoolSize_: " + enforceMaxPoolSize_); logInformation("pretestConnections_: " + pretestConnections_); logInformation("reuseConnections_: " + reuseConnections_); logInformation("maxIdleTime_: " + maxIdleTime_ + " msecs"); logInformation("maxLifetime_: " + maxLifetime_ + " msecs"); logInformation("propertyCycle_: " + propertyCycle_ + " msecs"); logInformation("server name: |" + cpds_.getServerName() + "|"); logInformation("default user: |" + cpds_.getUser() + "|"); } if (GATHER_STATS) { System.out.println("initialPoolSize_: " + initialPoolSize_); System.out.println("minPoolSize_: " + minPoolSize_); System.out.println("maxPoolSize_: " + maxPoolSize_); System.out.println("enforceMaxPoolSize_: " + enforceMaxPoolSize_); System.out.println("pretestConnections_: " + pretestConnections_); System.out.println("reuseConnections_: " + reuseConnections_); System.out.println("maxIdleTime_: " + maxIdleTime_ + " msecs"); System.out.println("maxLifetime_: " + maxLifetime_ + " msecs"); System.out.println("propertyCycle_: " + propertyCycle_ + " msecs"); System.out.println("server name: |" + cpds_.getServerName() + "|"); System.out.println("default user: |" + cpds_.getUser() + "|"); } maintainerInterval_ = propertyCycle_; reaperInterval_ = 1800*1000; // at least once every 30 minutes (reasonable value) scavengerInterval_ = maintainerInterval_ * 20; // at least once every 20 maint cycles if (maxLifetime_ == 0) connectionLifetimeLimited_ = false; else { connectionLifetimeLimited_ = true; reaperInterval_ = maxLifetime_ / 3; // reasonable value } minSwapInterval_ = 50; // no more often than once every 50 msecs (reasonable value) if (maxPoolSize_ == 0) { poolSizeLimited_ = false; } else { poolSizeLimited_ = true; if (maxPoolSize_ < minPoolSize_) { if (JDTrace.isTraceOn()) logWarning("minPoolSize ("+minPoolSize_+") exceeds maxPoolSize ("+maxPoolSize_+")"); maxPoolSize_ = minPoolSize_ + 5; // a reasonable value } // Handle the initPoolSize being out of range. if (initialPoolSize_ > maxPoolSize_) { if (JDTrace.isTraceOn()) { logWarning("initialPoolSize ("+initialPoolSize_+") exceeds maxPoolSize ("+maxPoolSize_+")"); } initialPoolSize_ = minPoolSize_; } } // Note: Tolerate initialPoolSize_ < minPoolSize_. condemnedListLengthThreshold_ = Math.max(3, minPoolSize_/50); // # of 'condemned' connections that triggers an immediate cleanup minDefaultStackSize_ = (reuseConnections_ ? 4 : 1); // Fill the pool with the number of connections specified for initial pool size. // We distribute the new connections evenly between the two 'sides'. fillPool(Math.max(1,initialPoolSize_/2), FOREGROUND); // at least 1 connection fillPool(initialPoolSize_/2, BACKGROUND); // Create and start the maintainer daemon. poolMaintainer_ = new JDPoolMaintainer(); maintainerDaemon_ = new Thread(poolMaintainer_, "PoolMaintainerDaemon"); maintainerDaemon_.setDaemon(true); maintainerDaemon_.start(); // Create and start the scavenger daemon. poolScavenger_ = new JDPoolScavenger(); scavengerDaemon_ = new Thread(poolScavenger_, "PoolScavengerDaemon"); scavengerDaemon_.setDaemon(true); scavengerDaemon_.start(); // If connections have a maximum lifetime, create and start the reaper daemon. if (connectionLifetimeLimited_) { poolReaper_ = new JDPoolReaper(); reaperDaemon_ = new Thread(poolReaper_, "PoolReaperDaemon"); reaperDaemon_.setDaemon(true); reaperDaemon_.start(); } } // Verifies that all the daemons are still running. private final boolean areDaemonsAlive() { boolean ok = true; if (!maintainerDaemon_.isAlive()) { ok = false; logError("The maintainerDaemon_ is no longer running"); } if (!scavengerDaemon_.isAlive()) { ok = false; logError("The scavengerDaemon_ is no longer running"); } if (connectionLifetimeLimited_ && !reaperDaemon_.isAlive()) { ok = false; logError("The reaperDaemon_ is no longer running"); } return ok; } // Checks the health of the connection pool: That all the connection count is consistent with the list lengths, and that all the daemons are still running. // Future enhancement: Call this method periodically from one of the daemons. final boolean checkHealth(boolean logStatistics) { boolean ok = true; // First see if another thread is already running this method; and if so, just return true. boolean alreadyChecking = healthCheckInProgress_; if (!alreadyChecking) { synchronized (healthCheckLock_) { if (healthCheckInProgress_) alreadyChecking = true; else healthCheckInProgress_ = true; } } if (alreadyChecking) { if (DEBUG) logDiagnostic("checkHealth is already in progress"); return true; } try { // First pause everything. pausePool(); try { Thread.sleep(1500); // Let everything reach a pause point. } catch (InterruptedException ie) {} // ignore if (swapInProgress_) // swaps ignore pause requests { try { Thread.sleep(20); // Let the swap finish. } catch (InterruptedException ie) {} // ignore } if (swapInProgress_ && DEBUG) { logError("checkHealth(): swapInProgress_ flag is still on after 50 msec wait."); } // Lock all the lists and counts. // Then check that everything looks reasonable and consistent. synchronized (activeConnections_) { synchronized (availableConnections_[FOREGROUND]) { synchronized (availableConnections_[BACKGROUND]) { synchronized (condemnedConnections_) { synchronized (connectionsCreatedLock_) { synchronized (connectionsDestroyedLock_) { // Count all connections on the 'available' list. int totalAvailConnCount = 0; for (int side = FOREGROUND; side <= BACKGROUND; side++) { int availConnCount = 0; // Get list of all connection keys in the 'available' HashMap. JDConnectionPoolKey[] poolKeys = (JDConnectionPoolKey[])availableConnections_[side].keySet().toArray(new JDConnectionPoolKey[0]); for (int i=0; i maxPoolSize_)) { if (DEBUG) logDiagnostic("The requested number of connections ("+numConnectionsToAdd+") would cause the connection pool to exceed its maximum size"); numConnectionsToAdd = maxPoolSize_ - getConnectionCount(SYNC_NONE); } if (numConnectionsToAdd < 1) { if (DEBUG) logDiagnostic("fillPool() is returning because pool is already full"); return; } // Prepare to add connections to the pool. Pre-create some new connections, outside of the sync block. JDConnectionPoolKey poolKey = cpds_.getConnectionPoolKey(); // default key AS400JDBCPooledConnection[] newConnections = null; int numberOfConnsCreatedForThisRequest = 0; int numConnectionsAddedToPool = 0; // Implementation note: The creation of new physical connections can be a slow process. // We don't want checkHealth() to run while we're filling the pool, since the connection counts may temporarily appear to be inconsistent. pauseIfPoolPaused(0); // if pool is paused, wait until unpause() is called fillingPool_ = true; try { newConnections = new AS400JDBCPooledConnection[numConnectionsToAdd]; // If you cannot get a connection using a userid/password, do not fill the // pool at this time. String user = cpds_.getUser(); if ( user != null && user.length() > 0) { try { for (int i=0; i numConnectionsAddedToPool) { if (DEBUG) { logDiagnostic("newConnections.length=="+newConnections.length+"; numConnectionsAddedToPool=="+numConnectionsAddedToPool+"; need to close " + (numberOfConnsCreatedForThisRequest - numConnectionsAddedToPool) + " connections"); } for (int i=numConnectionsAddedToPool; i= maxPoolSize_) { if (DEBUG || GATHER_STATS) { System.out.println("\nPOOL IS FULL: " + 100*getConnectionCount(SYNC_NONE)/maxPoolSize_ + "% ALLOCATED"); } return true; } else return false; } // Returns true if pool contains more than maxPoolSize_ connections. private final boolean isPoolOverFull() { if (poolSizeLimited_ && (getConnectionCount(SYNC_NONE) > maxPoolSize_)) { return true; } else return false; } // Logs a diagnostic message. private final void logDiagnostic(String text) { logger_.logDiagnostic(text); } // Logs an error message. private final void logError(String text) { logger_.logError(text); } // Logs an exception. private final void logException(String text, Exception e) { logger_.logException(text, e); } // Logs an informational message. private final void logInformation(String text) { logger_.logInformation(text); } // Logs a warning message. private final void logWarning(String text) { logger_.logWarning(text); } // Pause if the pool's "pause" flag is up. // Note: Never call this method while inside a sync block, otherwise you're likely to hang. private final void pauseIfPoolPaused(long maxTimeToPause) { while (poolPaused_) { if (DEBUG) logDiagnostic("Pool is paused"); try { synchronized (poolPauseLock_) { if (poolPaused_) poolPauseLock_.wait(maxTimeToPause); } } catch (InterruptedException ie) {} // ignore } } // Pauses all threads manipulating the connection lists. // Caution: If the pool is paused but not subsequently unpaused, all threads using the pool will hang, and the PoolReaper would eventually clean up connections in the pool as their lifetimes expire. //
Note: Never call this method while inside a sync block, otherwise a hang is likely to result. private final void pausePool() { poolPaused_ = true; synchronized (poolPauseLock_) { poolPaused_ = true; // ensure that it ends up 'on' } } // A quick-and-dirty precheck to see if the background 'available' list is longer than the foreground list. private final boolean isBackgroundListLongerThanForegroundList(JDConnectionPoolKey poolKey) { // It only makes sense to swap lists if there are more available connections (for desired key) in background list than in foreground list. // Design note: For performance, we do _not_ necessarily synchronize access to lists here. // If running unsynchronized, we may occasionally get a false positive or negative (for example if the lists get modified or swapped underneath us), but that won't cause any real damage. Stack backgroundStack = (Stack)availableConnections_[BACKGROUND].get(poolKey); if (backgroundStack != null && backgroundStack.size() != 0) { Stack foregroundStack = (Stack)availableConnections_[FOREGROUND].get(poolKey); if (foregroundStack == null || (foregroundStack.size() < backgroundStack.size())) { return true; } } return false; } // Utility method. Assumes that the caller has locked the lists. private final void swapAvailLists() { // Swap the foreground and background 'available' lists. availableConnections_[HOLD] = availableConnections_[FOREGROUND]; availableConnections_[FOREGROUND] = availableConnections_[BACKGROUND]; availableConnections_[BACKGROUND] = availableConnections_[HOLD]; availableConnections_[HOLD] = null; // Swap the foreground and background 'idled sequence' lists. availableConnectionsIdledSequence_[HOLD] = availableConnectionsIdledSequence_[FOREGROUND]; availableConnectionsIdledSequence_[FOREGROUND] = availableConnectionsIdledSequence_[BACKGROUND]; availableConnectionsIdledSequence_[BACKGROUND] = availableConnectionsIdledSequence_[HOLD]; availableConnectionsIdledSequence_[HOLD] = null; } // Utility method. // Swaps the foreground and background 'available' connection lists. // Returns true if the swap succeeded; false if the swap wasn't performed. // The swap may fail, for example, if: // (1) a swap is already in progress; or // (2) the background list (for specified key) isn't longer than the foreground list; or // (3) the request is from the foreground process, and the poolMaintainer daemon currently has the background list locked; or // (4) the previous swap happened too recently. // If the swap fails, this method exits and returns false. // If the foreground process is requesting the swap, we ensure that the swap either succeeds quickly or fails quickly. private final boolean swapConnectionLists(int sideRequestingSwap, JDConnectionPoolKey poolKey) { if (GATHER_STATS) swapsAttempted_++; boolean swapped = false; boolean alreadySwapping = swapInProgress_; try { if (!alreadySwapping) { // If we recently performed a swap attempt, reject this request. if (System.currentTimeMillis() - timeLastSwapAttempted_ < minSwapInterval_) { if (DEBUG || GATHER_STATS) swapsFailed_premature_++; if (DEBUG) logDiagnostic("Premature swap request rejected"); return false; // Bail out now. } // Precheck the foreground/background lists, to see if it's worth even trying to swap. if (!isBackgroundListLongerThanForegroundList(poolKey)) { if (DEBUG) logDiagnostic("Swap request rejected because background list is not longer than foreground list"); if (GATHER_STATS) swapsFailed_notWorthIt_++; return false; // Bail out now. } synchronized (swapLock_) { // Grab lock, and check again. if (swapInProgress_) alreadySwapping = true; else swapInProgress_ = true; } } if (alreadySwapping) { if (DEBUG) logDiagnostic("Swap request rejected because swap already in progress"); if (GATHER_STATS) { if (sideRequestingSwap == FOREGROUND) swapsFailed_foreground_inProgress_++; else swapsFailed_background_inProgress_++; } return false; // Bail out now. } // If we got this far, we've determined that a swap isn't already in progress, // and we've turned on the swapInProgress_ flag ourselves. try { // Note: Never pause during a list swap. switch (sideRequestingSwap) { case BACKGROUND: // The maintainer daemon is requesting the swap. { // Just grab the necessary locks and do the swap. synchronized (availableConnections_[FOREGROUND]) { if (poolClosed_) return false; synchronized (availableConnections_[BACKGROUND]) { // If we got this far, proceed with the swap (don't check poolClosed_). timeLastSwapAttempted_ = System.currentTimeMillis(); // It only makes sense to swap if there are more available connections (for desired key) in background list than in foreground list. if (isBackgroundListLongerThanForegroundList(poolKey)) { swapAvailLists(); swapped = true; if (DEBUG || GATHER_STATS) { swapsSucceeded_background_++; } } else { // Background list isn't longer than foreground list. if (DEBUG || GATHER_STATS) { swapsFailed_background_++; } if (DEBUG) logDiagnostic("Swap requested by daemon rejected because background list is not longer than foreground list"); } } } // outermost sync block } break; case FOREGROUND: // The foreground process is requesting the swap. { // Fail-fast if maintainer daemon is currently awake, so we don't wait for locks. if (poolMaintainer_.isAwake_) { if (DEBUG || GATHER_STATS) swapsFailed_foreground_daemonAwake_++; if (DEBUG) logDiagnostic("Foreground swap request rejected because daemon is awake"); return false; } synchronized (maintainerSnoozeLock_) { poolMaintainer_.snooze_ = true; // Instruct daemon to go dormant if it wakes up. // We must prevent the maintainer thread from waking up, locking the background list, then attempting a swap, since that could cause a deadlock. } try { if (poolMaintainer_.isAwake_) { if (DEBUG || GATHER_STATS) swapsFailed_foreground_daemonAwake_++; if (DEBUG) logDiagnostic("Foreground swap request rejected because daemon is awake"); return false; } // Daemon is still sleeping, so it's safe to grab locks. synchronized (availableConnections_[FOREGROUND]) { if (poolClosed_) return false; synchronized (availableConnections_[BACKGROUND]) { // If we got this far, go ahead and do the swap (don't check poolClosed_). timeLastSwapAttempted_ = System.currentTimeMillis(); // It only makes sense to swap if there are more available connections (for desired key) in background list than in foreground list. if (isBackgroundListLongerThanForegroundList(poolKey)) { swapAvailLists(); swapped = true; if (DEBUG || GATHER_STATS) swapsSucceeded_foreground_++; } else { // Background list isn't longer than foreground list. if (DEBUG || GATHER_STATS) swapsFailed_foreground_++; if (DEBUG) logDiagnostic("Swap request rejected because background list is not longer than foreground list"); } } } // outermost sync block } finally { // Tell the maintainer daemon to stop snoozing. poolMaintainer_.snooze_ = false; synchronized (maintainerSnoozeLock_) { poolMaintainer_.snooze_ = false; // just to be sure maintainerSnoozeLock_.notify(); } } } break; default: // neither FOREGROUND nor BACKGROUND { // Internal design error. // Rather than crashing, attempt a fail-fast swap. logError("Internal error: JDConnectionPoolManager.swapConnectionLists("+sideRequestingSwap+")" + sideRequestingSwap); swapConnectionLists(FOREGROUND, poolKey); } break; } // switch } // inner try finally { if (swapped) { needMoreConnections_ = false; } swapInProgress_ = false; // we turned the flag on, so now we need to turn it off } } // outer try finally { if (GATHER_STATS) { if (swapped) swapsSucceeded_++; else swapsFailed_++; } } return swapped; } /** Unpauses all threads manipulating the connection lists. Cancels the effects of a prior call to pausePool(). @see #pausePool() **/ private final void unpausePool() { poolPaused_ = false; synchronized (poolPauseLock_) { poolPaused_ = false; // ensure that it stays off poolPauseLock_.notifyAll(); } } private final void wakeMaintainerDaemon() { synchronized (maintainerSleepLock_) { maintainerSleepLock_.notify(); } } private final void wakeReaperDaemon() { synchronized (reaperSleepLock_) { reaperSleepLock_.notify(); } } private final void wakeScavengerDaemon() { synchronized (scavengerSleepLock_) { scavengerSleepLock_.notify(); } } // JDAgeComparator -------------------------------------------------------------------- /** Helper class. This comparator compares two AS400JDBCPooledConnection objects based on their age (creation dates). **/ private final class JDAgeComparator implements java.util.Comparator { /** Compares two AS400JDBCPooledConnection objects, based on their creation dates. @return -1, 0, or +1 if the first argument is less than, equal to, or greater than the second. @throws IllegalArgumentException if the arguments aren't instances of AS400JDBCPooledConnection. **/ public int compare(Object o1, Object o2) { try { long creationTime1 = ((AS400JDBCPooledConnection)o1).timeWhenCreated_; long creationTime2 = ((AS400JDBCPooledConnection)o2).timeWhenCreated_; if (creationTime1 < creationTime2) return -1; else if (creationTime1 > creationTime2) return 1; else // the two objects were created at the same time { // We must not report "equal" if they are different objects, otherwise the TreeSet will overlay one of them with the other. if (o1.hashCode() < o2.hashCode()) return -1; else if (o1.hashCode() > o2.hashCode()) return 1; else return 0; // If hashcodes match, and created at same time, assume the objects are identical. } } catch (ClassCastException e) { logException("Exception when comparing connections", e); throw new InternalErrorException(InternalErrorException.UNKNOWN); } } } // internal class JDAgeComparator // JDPoolMaintainer -------------------------------------------------------------------- /** Helper class. This daemon wakes up every maintainerInterval_ msecs and performs maintainenance on the current backup 'available connection' list. It performs a list swap if needed. It also identifies any stale connections in the avail list and moves them the 'condemned' list. **/ private final class JDPoolMaintainer implements Runnable { private final static String DAEMON_NAME = "Maintainer daemon"; boolean isAwake_ = true; // false if this daemon is sleeping, true otherwise boolean snooze_ = false; // tells this daemon to "sleep late" if it wakes up /** The main job of this daemon is to renew/replenish the 'available' lists as needed. **/ public void run() { final JDConnectionPoolKey[] dummyKeyArray_ = new JDConnectionPoolKey[0]; // for toArray() logInformation(DAEMON_NAME + " started with maintainerInterval_ = "+maintainerInterval_+" msecs and maxIdleTime_ = "+maxIdleTime_+" msecs"); ArrayList candidatesForRemoval = new ArrayList(minPoolSize_); // reasonable initial size int numCondemnedConnections = 0; long previousRunTime = 0; // duration of the previous cycle's maintenance activity long runStartTime = 0; try { while (keepDaemonsAlive_) { if (GATHER_STATS) System.out.print("(m)"); isAwake_ = true; try { long timeToSleep = maintainerInterval_ - previousRunTime; if (timeToSleep > 0) { isAwake_ = false; try { synchronized (maintainerSleepLock_) { maintainerSleepLock_.wait(timeToSleep); isAwake_ = true; } } catch (InterruptedException ie) {} // ignore finally { isAwake_ = true; } // just to be sure } else { if (JDTrace.isTraceOn()) logWarning(DAEMON_NAME + " did not sleep"); } while (snooze_) { isAwake_ = false; try { synchronized (maintainerSnoozeLock_) { if (snooze_) maintainerSnoozeLock_.wait(); isAwake_ = true; } } catch (InterruptedException ie) {} // ignore finally { isAwake_ = true; } // just to be sure } if (!keepDaemonsAlive_) break; if (DEBUG) logInformation(DAEMON_NAME + " woke up"); if (DEBUG || GATHER_STATS) maintainerDaemonCycles_++; pauseIfPoolPaused(0); // if pool is paused, wait until unpause() is called runStartTime = System.currentTimeMillis(); if (GATHER_STATS) System.out.print("(M)"); // Perform sequence of tasks, in separate try blocks. That way, if an exception occurs in an early task, the final tasks in the loop don't get skipped. // Warn if there are 'active but in error' connections. if (isPoolFull() || activeConnectionsInError_.size() > 5) { if (activeConnectionsInError_.size() > 1) logWarning(activeConnectionsInError_.size() + " connections have experienced fatal connection errors, but are still held by the application."); } // Identify and remove any stale connections in the 'available' list. try { // Figure out our high water mark. If lastUsed_ // on a pooled connection is before this value, that // means the connection has not been touched in // a longer time than is allowed and we need to close // it down and remove it. long cutoffTime = System.currentTimeMillis() - maxIdleTime_; candidatesForRemoval.clear(); // Identify any stale connections, and move them from the 'available' list to the candidatesForRemoval list. synchronized (availableConnections_[BACKGROUND]) { if (!keepDaemonsAlive_) break; // Get list of all connection keys in the 'available' HashMap. JDConnectionPoolKey[] poolKeys = (JDConnectionPoolKey[])availableConnections_[BACKGROUND].keySet().toArray(dummyKeyArray_); for (int i=0; i 0) { fillPool(numToAdd, BACKGROUND); } // See if the foreground process has indicated that we need to do a list swap. if (needMoreConnections_) { // Request a swap. This may take awhile, but the daemon is not in a hurry. // Note that the swap may fail if background list is not longer than foreground list. if (DEBUG) logInformation(DAEMON_NAME + " is requesting a swap; needMoreConnections_ =="+needMoreConnections_); JDConnectionPoolKey defaultPoolKey = cpds_.getConnectionPoolKey(); swapConnectionLists(BACKGROUND, defaultPoolKey); } } catch (Exception e) { if (JDTrace.isTraceOn()) logException("Exception caught by " + DAEMON_NAME, e); // Keep running. } // If needed, wake up the other daemons to close condemned or expired connections. if (numCondemnedConnections > condemnedListLengthThreshold_) { wakeScavengerDaemon(); } else if (isPoolOverFull()) { wakeReaperDaemon(); } } // inner try catch (Exception e) { if (JDTrace.isTraceOn()) logException ("Exception caught by " + DAEMON_NAME, e); // Keep running. } finally { isAwake_ = true; previousRunTime = System.currentTimeMillis() - runStartTime; minSwapInterval_ = Math.max(50, previousRunTime / 2); } } // while keepDaemonsAlive_ } // outermost try finally { logInformation(DAEMON_NAME + " has stopped"); isAwake_ = false; } } } // internal class JDPoolMaintainer // JDPoolReaper -------------------------------------------------------------------- /** Helper class. This daemon wakes up every reaperInterval_ milliseconds and scans the list of active connections. If a connection is expired, PoolReaper moves it from the 'active' list to the 'condemned' list. Note: This daemon is _not_ started if connections have "unlimited lifetime". **/ private final class JDPoolReaper implements Runnable { private final static String DAEMON_NAME = "Reaper daemon"; public void run() { logInformation(DAEMON_NAME + " started with reaperInterval_ = "+reaperInterval_+" msecs and maxLifetime_ = "+maxLifetime_+" msecs"); ArrayList candidatesForRemoval = new ArrayList(minPoolSize_); // reasonable initial size long previousRunTime = 0; // duration of the previous cycle's maintenance activity long runStartTime = 0; try { while (keepDaemonsAlive_) { if (GATHER_STATS) System.out.print("(r)"); try { long timeToSleep = reaperInterval_ - previousRunTime; if (timeToSleep > 0) { try { synchronized (reaperSleepLock_) { reaperSleepLock_.wait(timeToSleep); } } catch (InterruptedException ie) {} // ignore if (DEBUG) logInformation(DAEMON_NAME + " emerged from wait()"); } else { if (JDTrace.isTraceOn()) logWarning(DAEMON_NAME + " did not sleep"); } if (!keepDaemonsAlive_) break; if (DEBUG) logInformation(DAEMON_NAME + " woke up"); if (DEBUG || GATHER_STATS) reaperDaemonCycles_++; pauseIfPoolPaused(reaperInterval_); // don't risk pausing forever runStartTime = System.currentTimeMillis(); if (GATHER_STATS) System.out.print("(R)"); // Identify any expired connections in the 'active' list, and move them to 'condemned' list. long cutoffTime = runStartTime - maxLifetime_; // Any connections allocated prior to this moment in time are considered expired, and are subject to forced disconnection. candidatesForRemoval.clear(); synchronized (activeConnections_) { if (!keepDaemonsAlive_) break; Iterator activeIter = activeConnections_.iterator(); while (activeIter.hasNext()) { AS400JDBCPooledConnection conn = (AS400JDBCPooledConnection)activeIter.next(); if (isExpired(conn, cutoffTime)) { if (isStale(conn, runStartTime - maxIdleTime_)) // Don't reap connections that were recently allocated. { if (DEBUG || GATHER_STATS) expiredConnectionsIdentifiedByReaper_++; activeIter.remove(); // Remove connection from the 'active' list. if (conn.fatalConnectionErrorOccurred_) { activeConnectionsInError_.remove(conn); // ... and also from 'error' list } candidatesForRemoval.add(conn); // ... and add it to the 'condemned' list. } } else break; // This list is sorted in ascending order of connection creation time. As we move forward in the list: once we find a non-expired connection, we're done with the list. } // while } // sync if (DEBUG || GATHER_STATS) { if (candidatesForRemoval.size() != 0) { System.out.println("\n(CLEANUP)"+DAEMON_NAME+" has added " + candidatesForRemoval.size() + " expired connections to condemned list."); } } // Move any staged expired connections to the 'condemned' list. Iterator candidatesIter = candidatesForRemoval.iterator(); int numCondemnedConnections; synchronized (condemnedConnections_) { // Don't leave connections in limbo. while (candidatesIter.hasNext()) { AS400JDBCPooledConnection conn = (AS400JDBCPooledConnection)candidatesIter.next(); if (JDTrace.isTraceOn()) logDiagnostic(DAEMON_NAME+" is closing an active connection that has exceeded the maximum lifetime: " + conn.toString()); condemnedConnections_.add(conn); candidatesIter.remove(); // remove connection from candidatesIter list } numCondemnedConnections = condemnedConnections_.size(); } // If needed, wake up the scavenger daemon to close condemned physical connections. if (numCondemnedConnections > condemnedListLengthThreshold_) { wakeScavengerDaemon(); } } // inner try catch (Exception e) { if (JDTrace.isTraceOn()) logException("Exception caught by " + DAEMON_NAME, e); } finally { previousRunTime = System.currentTimeMillis() - runStartTime; } } // while keepDaemonsAlive_ } // outermost try finally { logInformation(DAEMON_NAME + " has stopped"); } } } // internal class JDPoolReaper // JDPoolScavenger -------------------------------------------------------------------- /** Helper class. This daemon wakes up every 'scavengerInterval_' milliseconds and closes all the connections in the 'condemned' list. **/ private final class JDPoolScavenger implements Runnable { private final static String DAEMON_NAME = "Scavenger daemon"; public void run() { logInformation(DAEMON_NAME + " started with scavengerInterval_ = "+scavengerInterval_+" msecs"); ArrayList candidatesForRemoval = new ArrayList(minPoolSize_); // reasonable initial size long previousRunTime = 0; // duration of the previous cycle's maintenance activity long runStartTime = 0; try { while (keepDaemonsAlive_) { if (GATHER_STATS) System.out.print("(s)"); try { long timeToSleep = scavengerInterval_ - previousRunTime; if (timeToSleep > 0) { try { synchronized (scavengerSleepLock_) { scavengerSleepLock_.wait(timeToSleep); } } catch (InterruptedException ie) {} // ignore if (DEBUG) logInformation(DAEMON_NAME + " emerged from wait()"); } else { if (JDTrace.isTraceOn()) logWarning(DAEMON_NAME + " did not sleep"); } if (!keepDaemonsAlive_) break; if (DEBUG) logInformation(DAEMON_NAME + " woke up"); if (DEBUG || GATHER_STATS) scavengerDaemonCycles_++; pauseIfPoolPaused(0); // if pool is paused, wait until unpause() is called runStartTime = System.currentTimeMillis(); if (GATHER_STATS) System.out.print("(+S)"); // Close all connections in the 'condemned' list. synchronized (condemnedConnections_) { if (!keepDaemonsAlive_) break; Iterator condemnedIter = condemnedConnections_.iterator(); while (condemnedIter.hasNext()) { AS400JDBCPooledConnection conn = (AS400JDBCPooledConnection)condemnedIter.next(); condemnedIter.remove(); candidatesForRemoval.add(conn); } } if (DEBUG || GATHER_STATS) { if (candidatesForRemoval.size() != 0) { System.out.println("\n(CLEANUP)"+DAEMON_NAME+" is closing " + candidatesForRemoval.size() + " condemned connections."); } } // Don't leave connections in limbo. Iterator candidatesIter = candidatesForRemoval.iterator(); while (candidatesIter.hasNext()) { closePhysicalConnection((AS400JDBCPooledConnection)candidatesIter.next()); if (DEBUG || GATHER_STATS) condemnedConnectionsRemoved_++; candidatesIter.remove(); // remove connection from candidatesForRemoval list } if (DEBUG && getConnectionCount(SYNC_ALL) < 0) { logError(DAEMON_NAME + ": Total connection count is negative"); // This would indicate a design flaw. } } // inner try catch (Exception e) { if (JDTrace.isTraceOn()) logException("Exception caught by " + DAEMON_NAME, e); } finally { previousRunTime = System.currentTimeMillis() - runStartTime; // If needed, wake up the reaper daemon to close any expired connections. if (isPoolOverFull()) { wakeReaperDaemon(); } } } // while keepDaemonsAlive_ } // outermost try finally { logInformation(DAEMON_NAME + " has stopped"); } } } // internal class JDPoolScavenger } // class JDConnectionPoolManager




© 2015 - 2024 Weber Informatics LLC | Privacy Policy