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

com.sun.enterprise.resource.pool.ConnectionPool Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation.
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.enterprise.resource.pool;

import com.sun.appserv.connectors.internal.api.PoolingException;
import com.sun.enterprise.connectors.ConnectorConnectionPool;
import com.sun.enterprise.connectors.ConnectorRuntime;
import com.sun.enterprise.resource.ResourceHandle;
import com.sun.enterprise.resource.ResourceSpec;
import com.sun.enterprise.resource.ResourceState;
import com.sun.enterprise.resource.allocator.ResourceAllocator;
import com.sun.enterprise.resource.listener.PoolLifeCycleListener;
import com.sun.enterprise.resource.pool.datastructure.DataStructure;
import com.sun.enterprise.resource.pool.datastructure.DataStructureFactory;
import com.sun.enterprise.resource.pool.resizer.Resizer;
import com.sun.enterprise.resource.pool.waitqueue.PoolWaitQueue;
import com.sun.enterprise.resource.pool.waitqueue.PoolWaitQueueFactory;
import com.sun.enterprise.transaction.api.JavaEETransaction;
import com.sun.logging.LogDomains;

import jakarta.resource.ResourceException;
import jakarta.resource.spi.ManagedConnection;
import jakarta.resource.spi.RetryableUnavailableException;
import jakarta.transaction.Transaction;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

import javax.naming.NamingException;

import org.glassfish.resourcebase.resources.api.PoolInfo;

import static com.sun.appserv.connectors.internal.spi.BadConnectionEventListener.POOL_RECONFIGURED_ERROR_CODE;
import static com.sun.enterprise.connectors.service.ConnectorAdminServiceUtils.getReservePrefixedJNDINameForPool;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;

/**
 * Connection Pool for Connector & JDBC resources
* * @author Jagadish Ramu */ public class ConnectionPool implements ResourcePool, ConnectionLeakListener, ResourceHandler, PoolProperties { private static final Logger LOG = LogDomains.getLogger(ConnectionPool.class, LogDomains.RSR_LOGGER); // pool life-cycle config properties /** * Represents the "max-pool-size" configuration value.
* Specifies the maximum number of connections that can be created to satisfy client requests.
* Default: 32 */ protected int maxPoolSize; /** * Represents the "steady-pool-size" configuration value.
* Specifies the initial and minimum number of connections maintained in the pool.
* Default: 8 */ protected int steadyPoolSize; /** * Represents the "pool-resize-quantity" configuration value.
* Specifies the number of idle connections to be destroyed if the existing number of connections is above the * steady-pool-size (subject to the max-pool-size limit).
* This is enforced periodically at the idle-timeout-in-seconds interval. An idle connection is one that has not been * used for a period of idle-timeout-in-seconds. When the pool size reaches steady-pool-size, connection removal * stops.
* Default: 2 */ protected int resizeQuantity; /** * Represents the "max-wait-time-in-millis" configuration value.
* Specifies the amount of time, in milliseconds, that the caller is willing to wait for a connection. If 0, the caller * is blocked indefinitely until a resource is available or an error occurs.
* Default: 60.000ms */ protected int maxWaitTime; /** * Represents the "idle-timeout-in-seconds" configuration value, but in millis.
* Specifies the maximum time that a connection can remain idle in the pool. After this amount of time, the pool can * close this connection.
* Default: 300.000ms */ protected long idletime; // pool config properties /** * Represents the "fail-all-connections" configuration value.
* If true, closes all connections in the pool if a single validation check fails.
* Default: false */ protected boolean failAllConnections; /** * Represents the "match-connections" configuration value.
* If true, enables connection matching. You can set to false if connections are homogeneous.
* Default: true */ protected boolean matchConnections; /** * Represents the "is-connection-validation-required" configuration value.
* Specifies whether connections have to be validated before being given to the application. If a resource’s validation * fails, it is destroyed, and a new resource is created and returned.
* Default: false */ protected boolean connectionValidationRequired; /** * Represents the "prefer-validate-over-recreate property" configuration value.
* Specifies that validating idle connections is preferable to closing them. This property has no effect on non-idle * connections. If set to true, idle connections are validated during pool resizing, and only those found to be invalid * are destroyed and recreated. If false, all idle connections are destroyed and recreated during pool resizing.
* Default: false */ protected boolean preferValidateOverRecreate; /** * True if the pool is initialized */ protected volatile boolean poolInitialized; /** * Reference to a resizer task to be able to cancel/reschedule it. */ protected Resizer resizerTask; /** * Timer for the resizerTask. Runs every {@link #idletime} milliseconds. */ protected Timer resizerTaskTimer; // advanced pool config properties /** * Represents the "connection-creation-retry-attempts" configuration value.
* If the value connectionCreationRetryAttempts_ is > 0 then connectionCreationRetry_ is true, otherwise false. */ protected boolean connectionCreationRetry_; /** * Represents the "connection-creation-retry-attempts" configuration.
* Specifies the number of attempts to create a new connection.
* Default: 0 */ protected int connectionCreationRetryAttempts_; /** * Represents the "connection-creation-retry-interval-in-seconds" configuration, but in millis.
* Specifies the time interval between attempts to create a connection when connection-creation-retry-attempts is * greater than 0.
* Default: 10.000ms */ protected long conCreationRetryInterval_; /** * Represents the "validate-atmost-once-period-in-seconds" configuration, but in millis.
* Specifies the time interval within which a connection is validated at most once. Minimizes the number of validation * calls. A value of zero allows unlimited validation calls.
* This value is used when {@link #connectionValidationRequired} is set to true.
* Default: 10.000ms. */ protected long validateAtmostPeriodInMilliSeconds_; /** * Represents the "max-connection-usage-count" configuration.
* Specifies the number of times a connections is reused by the pool, after which it is closed. A zero value disables * this feature.
* Default: 0 */ protected int maxConnectionUsage_; /** * To validate a Sun RA Pool Connection if it has not been validated in the past x sec. (x=idle-timeout). The property * will be set from system property: com.sun.enterprise.connectors.ValidateAtmostEveryIdleSecs=true
* Default: false */ private boolean validateAtmostEveryIdleSecs; /** * Listener for pool monitoring statistics. */ protected PoolLifeCycleListener poolLifeCycleListener; // Gateway used to control the concurrency within the round-trip of resource access. protected ResourceGateway gateway; protected String resourceGatewayClass; protected ConnectionLeakDetector leakDetector; /** * The datastructure containing the pooled resources. */ protected DataStructure dataStructure; protected String dataStructureType; protected String dataStructureParameters; protected PoolWaitQueue waitQueue; protected PoolWaitQueue reconfigWaitQueue; private long reconfigWaitTime; protected String poolWaitQueueClass; protected final PoolInfo poolInfo; // poolName private final PoolTxHelper poolTxHelper; // NOTE: This resource allocator may not be the same as the allocator passed in to getResource() protected ResourceAllocator allocator; // TODO: selfManaged_ does not seem to be set to true in glassfish code. Remove it from ResourcePool interface. // Glassfish 2.1 had a management-rule / self-management part, but it no longer exists in domain.xml. // See for example package "selfmanagement" and logger "SELF_MANAGEMENT_LOGGER" in the codeline. private boolean selfManaged_; /** * Blocked state is true during connection pool recreation. Once blocked is set to true only resources from the pool can * be reused which are already part of the current transaction. */ private boolean blocked; /** * Reentrant lock to ensure calls to {@link #getResourceFromPool(ResourceAllocator, ResourceSpec)} and * {@link #freeResource(ResourceHandle)} are not executed at the same time to solve issue 24843, because one is getting * resources from the pool and possibly resizing the pool while the other is returning resources to the pool. */ private final ReentrantLock getResourceFromPoolAndFreeResourceMethodsLock = new ReentrantLock(true); public ConnectionPool(PoolInfo poolInfo, Hashtable env) throws PoolingException { this.poolInfo = poolInfo; setPoolConfiguration(env); initializePoolDataStructure(); initializeResourceSelectionStrategy(); initializePoolWaitQueue(); poolTxHelper = new PoolTxHelper(this.poolInfo); gateway = ResourceGateway.getInstance(resourceGatewayClass); LOG.log(FINE, "Connection Pool: {0}", this.poolInfo); } protected void initializePoolWaitQueue() throws PoolingException { waitQueue = PoolWaitQueueFactory.createPoolWaitQueue(poolWaitQueueClass); reconfigWaitQueue = PoolWaitQueueFactory.createPoolWaitQueue(poolWaitQueueClass); } protected void initializePoolDataStructure() throws PoolingException { dataStructure = DataStructureFactory.getDataStructure( dataStructureType, dataStructureParameters, maxPoolSize, this); } protected void initializeResourceSelectionStrategy() { // do nothing } private void setPoolConfiguration(Hashtable env) throws PoolingException { ConnectorConnectionPool poolResource = getPoolConfigurationFromJndi(env); idletime = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) * 1000L; maxPoolSize = Integer.parseInt(poolResource.getMaxPoolSize()); steadyPoolSize = Integer.parseInt(poolResource.getSteadyPoolSize()); if (maxPoolSize < steadyPoolSize) { maxPoolSize = steadyPoolSize; } resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity()); maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis()); // Make sure it's not negative. if (maxWaitTime < 0) { maxWaitTime = 0; } failAllConnections = poolResource.isFailAllConnections(); connectionValidationRequired = poolResource.isIsConnectionValidationRequired(); validateAtmostEveryIdleSecs = poolResource.isValidateAtmostEveryIdleSecs(); dataStructureType = poolResource.getPoolDataStructureType(); dataStructureParameters = poolResource.getDataStructureParameters(); poolWaitQueueClass = poolResource.getPoolWaitQueue(); resourceGatewayClass = poolResource.getResourceGatewayClass(); reconfigWaitTime = poolResource.getDynamicReconfigWaitTimeout(); setAdvancedPoolConfiguration(poolResource); } protected ConnectorConnectionPool getPoolConfigurationFromJndi(Hashtable env) throws PoolingException { try { return (ConnectorConnectionPool) ConnectorRuntime.getRuntime() .getResourceNamingService() .lookup( poolInfo, getReservePrefixedJNDINameForPool(poolInfo), env); } catch (NamingException ex) { throw new PoolingException(ex); } } // This method does not need to be synchronized since all caller methods are, // but it does not hurt. Just to be safe. protected synchronized void initPool(ResourceAllocator allocator) throws PoolingException { if (poolInitialized) { return; } this.allocator = allocator; createResources(this.allocator, steadyPoolSize - dataStructure.getResourcesSize()); // if the idle time out is 0, then don't schedule the resizer task if (idletime > 0) { scheduleResizerTask(); } // Need to set the numConnFree of monitoring statistics to the steadyPoolSize // as monitoring might be ON during the initialization of pool. // Need not worry about the numConnUsed here as it would be initialized to // 0 automatically. if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionsFreed(steadyPoolSize); } poolInitialized = true; } /** * Schedules the resizer timer task. If a task is currently scheduled, it would be cancelled and a new one is scheduled. *

* package protected for unit tests */ protected void scheduleResizerTask() { if (resizerTask != null) { // cancel the current task resizerTask.cancel(); resizerTask = null; } resizerTask = initializeResizer(); if (resizerTaskTimer == null) { resizerTaskTimer = ConnectorRuntime.getRuntime().getTimer(); } resizerTaskTimer.scheduleAtFixedRate(resizerTask, idletime, idletime); LOG.log(FINE, "Scheduled resizer task with the idle time {0} ms", idletime); } protected Resizer initializeResizer() { return new Resizer(poolInfo, dataStructure, this, this, preferValidateOverRecreate); } /** * Add a resource to the pool with status busy and not enlisted. * * @param alloc the ResourceAllocator to be used * @throws PoolingException when unable to add a resource */ private void addResource(ResourceAllocator alloc) throws PoolingException { int numResCreated = dataStructure.addResource(alloc, 1); if (numResCreated > 0) { for (int i = 0; i < numResCreated; i++) { if (poolLifeCycleListener != null) { poolLifeCycleListener.incrementNumConnFree(false, steadyPoolSize); } } } LOG.log(FINE, "Pool: resource added"); } /** * Marks resource as free. This method should be used instead of directly calling * resoureHandle.getResourceState().setBusy(false) OR getResourceState(resourceHandle).setBusy(false) as this method * handles stopping of connection leak tracing If connection leak tracing is enabled, takes care of stopping connection * leak tracing * * @param resourceHandle Resource */ protected void setResourceStateToFree(ResourceHandle resourceHandle) { getResourceState(resourceHandle).setBusy(false); leakDetector.stopConnectionLeakTracing(resourceHandle, this); } /** * Marks resource as busy. This method should be used instead of directly calling * resoureHandle.getResourceState().setBusy(true) OR getResourceState(resourceHandle).setBusy(true) as this method * handles starting of connection leak tracing If connection leak tracing is enabled, takes care of starting connection * leak tracing * * @param resourceHandle Resource */ protected void setResourceStateToBusy(ResourceHandle resourceHandle) { getResourceState(resourceHandle).setBusy(true); leakDetector.startConnectionLeakTracing(resourceHandle, this); } /** * Returns a resource from the connection pool. * * @return a free pooled resource object matching the ResourceSpec * @throws PoolingException - if any error occurs - or the pool has reached its max size and the * max-connection-wait-time-in-millis has expired. */ @Override public ResourceHandle getResource(ResourceSpec spec, ResourceAllocator alloc, Transaction transaction) throws PoolingException, RetryableUnavailableException { // Note: this method should not be synchronized or the // startTime would be incorrect for threads waiting to enter /* * Here are all the comments for the method put together for easy reference. 1. // - Try to get a free resource. Note: * internalGetResource() // will create a new resource if none is free and the max has // not been reached. // - If * can't get one, get on the wait queue. // - Repeat this until maxWaitTime expires. // - If maxWaitTime == 0, repeat * indefinitely. * * 2. //the doFailAllConnectionsProcessing method would already //have been invoked by now. //We simply go ahead and * create a new resource here //from the allocator that we have and adjust the resources //list accordingly so as to not * exceed the maxPoolSize ever //(i.e if steadyPoolSize == maxPoolSize ) ///Also since we are creating the resource out * of the allocator //that we came into this method with, we need not worry about //matching */ ResourceHandle result = null; long startTime = System.currentTimeMillis(); long elapsedWaitTime; long remainingWaitTime = 0; while (true) { if (gateway.allowed()) { // See comment #1 above JavaEETransaction javaEETransaction = ((JavaEETransaction) transaction); final Set resourcesSet = javaEETransaction == null ? null : javaEETransaction.getResources(poolInfo); // Allow when the pool is not blocked or at-least one resource is // already obtained in the current transaction. if (!blocked || (resourcesSet != null && !resourcesSet.isEmpty())) { try { result = internalGetResource(spec, alloc, transaction); } finally { gateway.acquiredResource(); } } } if (result != null) { // got one, return it if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionAcquired(result.getId()); elapsedWaitTime = System.currentTimeMillis() - startTime; poolLifeCycleListener.connectionRequestServed(elapsedWaitTime); if (LOG.isLoggable(FINE)) { LOG.log(FINE, "Resource Pool: elapsed time (ms) to get connection for [" + spec + "] : " + elapsedWaitTime); } } // got one - seems we are not doing validation or matching // return it break; } // did not get a resource. if (maxWaitTime > 0) { elapsedWaitTime = System.currentTimeMillis() - startTime; if (elapsedWaitTime < maxWaitTime) { // time has not expired, determine remaining wait time. remainingWaitTime = maxWaitTime - elapsedWaitTime; } else if (!blocked) { // wait time has expired if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionTimedOut(); } throw new PoolingException("No available resources and wait time " + maxWaitTime + " ms expired."); } } if (!blocked) { // add to wait-queue Object waitMonitor = new Object(); if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionRequestQueued(); } synchronized (waitMonitor) { waitQueue.addToQueue(waitMonitor); try { LOG.log(FINE, "Resource Pool: getting on wait queue"); waitMonitor.wait(remainingWaitTime); } catch (InterruptedException ex) { // Could be system shutdown. break; } // Try to remove in case that the monitor has timed out. We don't expect the queue to grow to great numbers // so the overhead for removing inexistant objects is low. LOG.log(FINE, "removing wait monitor from queue: {0}", waitMonitor); if (waitQueue.removeFromQueue(waitMonitor)) { if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionRequestDequeued(); } } } } else { // Add to reconfig-wait-queue Object reconfigWaitMonitor = new Object(); synchronized (reconfigWaitMonitor) { reconfigWaitQueue.addToQueue(reconfigWaitMonitor); try { if (reconfigWaitTime > 0) { LOG.log(FINEST, "[DRC] getting into reconfig wait queue for time [{0}]", reconfigWaitTime); reconfigWaitMonitor.wait(reconfigWaitTime); } } catch (InterruptedException ex) { // Could be system shutdown. break; } // Try to remove in case that the monitor has timed // out. We don't expect the queue to grow to great numbers // so the overhead for removing inexistent objects is low. LOG.log(FINEST, "[DRC] removing wait monitor from reconfig-wait-queue: {0}", reconfigWaitMonitor); reconfigWaitQueue.removeFromQueue(reconfigWaitMonitor); LOG.log(FINEST, "[DRC] throwing Retryable-Unavailable-Exception"); RetryableUnavailableException rue = new RetryableUnavailableException( "Pool Reconfigured, Connection Factory can retry the lookup"); rue.setErrorCode(POOL_RECONFIGURED_ERROR_CODE); throw rue; } } } alloc.fillInResourceObjects(result); return result; } /** * Overridden in AssocWithThreadResourcePool to fetch the resource cached in the ThreadLocal In ConnectionPool this * simply returns null. * * @param spec the ResourceSpec used to locate the correct resource pool * @param alloc ResourceAllocator to create a resource * @param tran Transaction * @return ResourceHandle resource from ThreadLocal */ protected ResourceHandle prefetch(ResourceSpec spec, ResourceAllocator alloc, Transaction tran) { return null; } protected ResourceHandle internalGetResource(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator, Transaction transaction) throws PoolingException { if (!poolInitialized) { initPool(resourceAllocator); } ResourceHandle resourceHandle = getResourceFromTransaction(transaction, resourceAllocator, resourceSpec); if (resourceHandle != null) { return resourceHandle; } resourceHandle = prefetch(resourceSpec, resourceAllocator, transaction); if (resourceHandle != null) { return resourceHandle; } // We didnt get a connection that is already enlisted in the current transaction (if any). resourceHandle = getUnenlistedResource(resourceSpec, resourceAllocator, transaction); if (resourceHandle != null) { if (maxConnectionUsage_ > 0) { resourceHandle.incrementUsageCount(); } if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionUsed(resourceHandle.getId()); // Decrement numConnFree poolLifeCycleListener.decrementNumConnFree(); } } return resourceHandle; } /** * Try to get a resource from current transaction if it is shareable
* * @param transaction Current Transaction * @param resourceAllocator ResourceAllocator * @param resourceSpec the ResourceSpec used to locate the correct resource pool * @return result ResourceHandle */ private ResourceHandle getResourceFromTransaction(Transaction transaction, ResourceAllocator resourceAllocator, ResourceSpec resourceSpec) { ResourceHandle resourceFromTransaction = null; try { // comment-1: sharing is possible only if caller is marked // shareable, so abort right here if that's not the case if (transaction != null && resourceAllocator.shareableWithinComponent()) { // TODO should be handled by PoolTxHelper JavaEETransaction javaEETransaction = (JavaEETransaction) transaction; // case 1. look for free and enlisted in same tx Set set = javaEETransaction.getResources(poolInfo); if (set != null) { Iterator iter = set.iterator(); while (iter.hasNext()) { ResourceHandle resourceHandle = (ResourceHandle) iter.next(); if (resourceHandle.hasConnectionErrorOccurred()) { iter.remove(); continue; } ResourceState state = resourceHandle.getResourceState(); /* * One can share a resource only for the following conditions: * * 1. The caller resource is shareable (look at the outermost if marked comment-1 * * 2. The resource enlisted inside the transaction is shareable * * 3. We are dealing with XA resources OR we are dealing with a non-XA resource that's not in use * Note that sharing a non-xa resource that's in use involves associating physical connections. * * 4. The credentials of the resources match */ if (resourceHandle.getResourceAllocator().shareableWithinComponent()) { if (resourceSpec.isXA() || poolTxHelper.isNonXAResourceAndFree(javaEETransaction, resourceHandle)) { if (matchConnections) { if (!resourceAllocator.matchConnection(resourceHandle)) { if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionNotMatched(); } continue; } if (resourceHandle.hasConnectionErrorOccurred()) { if (failAllConnections) { // if failAllConnections has happened, we flushed the // pool, so we don't have to do iter.remove else we // will get a ConncurrentModificationException resourceFromTransaction = null; break; } iter.remove(); continue; } if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionMatched(); } } // TODO: This 'if (state.isFree())' logic suggests the state can already be Busy. // Document why it can already be busy. // Is this because you can have a transaction within a transaction and reuse the same resource? // But in that case: shouldn't the state be already set to 'busy' by the previous code // in the same thread and still remain busy? if (state.isFree()) { setResourceStateToBusy(resourceHandle); } resourceFromTransaction = resourceHandle; break; } } } } } } catch (ClassCastException e) { if (LOG.isLoggable(FINE)) { LOG.log(FINE, "Pool: getResource : transaction is not JavaEETransaction but a " + transaction.getClass().getName(), e); } } return resourceFromTransaction; } /** * To provide an unenlisted, valid, matched resource from pool. * * @param resourceSpec the ResourceSpec used to locate the correct resource pool * @param resourceAllocator ResourceAllocator * @param transaction Transaction * @return ResourceHandle resource from pool * @throws PoolingException Exception while getting resource from pool */ protected ResourceHandle getUnenlistedResource(ResourceSpec resourceSpec, ResourceAllocator resourceAllocator, Transaction transaction) throws PoolingException { return getResourceFromPool(resourceAllocator, resourceSpec); } /** * Check whether the connection is valid * * @param resourceHandle Resource to be validated * @param resourceAllocator Allocator to validate the resource * @return boolean representing validation result */ protected boolean isConnectionValid(ResourceHandle resourceHandle, ResourceAllocator resourceAllocator) { boolean connectionValid = true; if (connectionValidationRequired || validateAtmostEveryIdleSecs) { long validationPeriod; // validation period is idle timeout if validateAtmostEveryIdleSecs is set to true // else it is validateAtmostPeriodInMilliSeconds_ if (connectionValidationRequired) { validationPeriod = validateAtmostPeriodInMilliSeconds_; } else { validationPeriod = idletime; } boolean validationRequired = true; long currentTime = resourceHandle.getLastValidated(); if (validationPeriod > 0) { currentTime = System.currentTimeMillis(); long timeSinceValidation = currentTime - resourceHandle.getLastValidated(); if (timeSinceValidation < validationPeriod) { validationRequired = false; } } if (validationRequired) { if (!resourceAllocator.isConnectionValid(resourceHandle)) { connectionValid = false; incrementNumConnFailedValidation(); } else { resourceHandle.setLastValidated(currentTime); } } } return connectionValid; } /** * check whether the connection retrieved from the pool matches with the request. * * @param resource Resource to be matched * @param alloc ResourceAllocator used to match the connection * @return boolean representing the match status of the connection */ protected boolean matchConnection(ResourceHandle resource, ResourceAllocator alloc) { // TODO: Explain what matching is in detail. // TODO: Explain that if matchConnections is disabled in the connectionpool why 'true' is still returned and not false?! // Old documentation mentions: "match-connections / default: true / If true, enables connection matching. You // can set to false if connections are homogeneous." Jakarta documentation: // jakarta.resource.spi.ManagedConnectionFactory.matchManagedConnections(Set, Subject, ConnectionRequestInfo) mentions: // "criteria used for matching is specific to a resource adapter and is not prescribed by the Connector specification." boolean matched = true; if (matchConnections) { matched = alloc.matchConnection(resource); if (poolLifeCycleListener != null) { if (matched) { poolLifeCycleListener.connectionMatched(); } else { poolLifeCycleListener.connectionNotMatched(); } } } return matched; } /** * return resource in free list. If none is found, try to scale up the pool/purge pool and
* return a new resource. returns null if the pool new resources cannot be created.
* * @param resourceAllocator ResourceAllocator * @param resourceSpec the ResourceSpec used to locate the correct resource pool * @return ResourceHandle resource from pool * @throws PoolingException if unable to create a new resource */ protected ResourceHandle getResourceFromPool(ResourceAllocator resourceAllocator, ResourceSpec resourceSpec) throws PoolingException { // The order of serving a resource request // 1. free and enlisted in the same transaction // 2. free and unenlisted // Do NOT give out a connection that is // free and enlisted in a different transaction ResourceHandle resourceFromPool = null; ResourceHandle resourceHandle; List freeResources = new ArrayList<>(); try { getResourceFromPoolAndFreeResourceMethodsLock.lock(); try { while ((resourceHandle = dataStructure.getResource()) != null) { if (resourceHandle.hasConnectionErrorOccurred()) { dataStructure.removeResource(resourceHandle); continue; } if (matchConnection(resourceHandle, resourceAllocator)) { boolean isValid = isConnectionValid(resourceHandle, resourceAllocator); if (resourceHandle.hasConnectionErrorOccurred() || !isValid) { if (failAllConnections) { resourceFromPool = createSingleResourceAndAdjustPool(resourceAllocator, resourceSpec); // No need to match since the resource is created with the allocator of caller. break; } dataStructure.removeResource(resourceHandle); // Resource is invalid, continue iteration. continue; } if (resourceHandle.isShareable() == resourceAllocator.shareableWithinComponent()) { // Got a matched, valid resource resourceFromPool = resourceHandle; break; } freeResources.add(resourceHandle); } else { freeResources.add(resourceHandle); } } } finally { // Return all unmatched, free resources for (ResourceHandle freeResource : freeResources) { dataStructure.returnResource(freeResource); } freeResources.clear(); } if (resourceFromPool != null) { // Set state to Busy setResourceStateToBusy(resourceFromPool); } else { // Set state to Busy via resizePoolAndGetNewResource call resourceFromPool = resizePoolAndGetNewResource(resourceAllocator); } } finally { getResourceFromPoolAndFreeResourceMethodsLock.unlock(); } return resourceFromPool; } /** * Scale-up the pool to serve the new request.
* If pool is at max-pool-size and free resources are found, purge unmatched
* resources, create new connections and serve the request.
* * @param resourceAllocator ResourceAllocator used to create new resources * @return ResourceHandle newly created resource * @throws PoolingException when not able to create resources */ private ResourceHandle resizePoolAndGetNewResource(ResourceAllocator resourceAllocator) throws PoolingException { // Must be called from the thread holding the lock to this pool. ResourceHandle newResource = null; int numOfConnsToCreate = 0; int dataStructureSize = dataStructure.getResourcesSize(); if (dataStructureSize < steadyPoolSize) { // May be all invalid resources are destroyed as // a result no free resource found and no. of resources is less than steady-pool-size numOfConnsToCreate = steadyPoolSize - dataStructureSize; } else if (dataStructureSize + resizeQuantity <= maxPoolSize) { // Create and add resources of quantity "resizeQuantity" numOfConnsToCreate = resizeQuantity; } else if (dataStructureSize < maxPoolSize) { // This else if "test condition" is not needed. Just to be safe. // still few more connections (less than "resizeQuantity" and to reach the count of maxPoolSize) // can be added numOfConnsToCreate = maxPoolSize - dataStructureSize; } if (numOfConnsToCreate > 0) { createResources(resourceAllocator, numOfConnsToCreate); newResource = getMatchedResourceFromPool(resourceAllocator); } else if (dataStructure.getFreeListSize() > 0) { // pool cannot create more connections as it is at max-pool-size. // If there are free resources at max-pool-size, then none of the free resources // has matched this allocator's request (credential). Hence purge free resources // of size <=resizeQuantity if (purgeResources(resizeQuantity) > 0) { newResource = resizePoolAndGetNewResource(resourceAllocator); } } return newResource; } // TODO can't this be replaced by getResourceFromPool ? private ResourceHandle getMatchedResourceFromPool(ResourceAllocator alloc) { ResourceHandle matchedResourceFromPool = null; List activeResources = new ArrayList<>(); try { ResourceHandle handle; while ((handle = dataStructure.getResource()) != null) { if (matchConnection(handle, alloc)) { matchedResourceFromPool = handle; // TODO: ensure the state is not already isBusy here setResourceStateToBusy(matchedResourceFromPool); // Break from the while loop and do not add the handle to the activeResources list. break; } activeResources.add(handle); } } finally { // Return unmatched resources for (ResourceHandle activeResource : activeResources) { dataStructure.returnResource(activeResource); } // No need to clear the list, clear() call is probably here to try to help the garbage collector. activeResources.clear(); } return matchedResourceFromPool; } /** * Try to purge resources by size <= quantity * * @param quantity maximum no. of resources to remove.
* @return resourceCount No. of resources actually removed.
*/ private int purgeResources(int quantity) { // Must be called from the thread holding the lock to this pool. int totalResourcesRemoved = 0; int freeResourcesCount = dataStructure.getFreeListSize(); int resourcesCount = (freeResourcesCount >= quantity) ? quantity : freeResourcesCount; LOG.log(FINE, "Purging resources of size: {0}", resourcesCount); for (int i = resourcesCount - 1; i >= 0; i--) { ResourceHandle resource = dataStructure.getResource(); if (resource != null) { dataStructure.removeResource(resource); totalResourcesRemoved += 1; } } return totalResourcesRemoved; } /** * This method will be called from the getUnenlistedResource method if we detect a failAllConnection flag. Here we * simply create a new resource and replace a free resource in the pool by this resource and then give it out. This * replacement is required since the steadypoolsize might equal maxpoolsize and in that case if we were not to remove a * resource from the pool, our resource would be above maxPoolSize * * @param resourceAllocator the resource allocator to be used to create the new resource * @param resourceSpec the ResourceSpec used to locate the correct resource pool * @return newly created resource * @throws PoolingException when unable to create a resource */ protected ResourceHandle createSingleResourceAndAdjustPool(ResourceAllocator resourceAllocator, ResourceSpec resourceSpec) throws PoolingException { // TODO document in getResource when a return value can be null ResourceHandle handle = dataStructure.getResource(); if (handle != null) { // TODO document why it is removed / add unit test to show the behavior described in the javadoc of the method dataStructure.removeResource(handle); } return getNewResource(resourceAllocator); } /** * Method to be used to create resource, instead of calling ResourceAllocator.createConfigBean(). This method handles * the connection creation retry in case of failure. * * @param resourceAllocator the resource allocator to be used to create the new resource * @return ResourceHandle newly created resource * @throws PoolingException when unable create a resource */ protected ResourceHandle createSingleResource(ResourceAllocator resourceAllocator) throws PoolingException { int count = 0; long startTime = System.currentTimeMillis(); while (true) { try { count++; ResourceHandle resourceHandle = resourceAllocator.createResource(); long now = System.currentTimeMillis(); LOG.log(FINE, () -> "Time taken to create a single resource: " + resourceHandle.getResourceSpec().getResourceId() + " and adding to the pool: " + (now - startTime) + " ms."); if (connectionValidationRequired || validateAtmostEveryIdleSecs) { resourceHandle.setLastValidated(now); } return resourceHandle; } catch (Exception ex) { if (!connectionCreationRetry_ || count > connectionCreationRetryAttempts_) { throw new PoolingException("Connection creation failed for " + count + " times.", ex); } LOG.log(WARNING, "Connection creation failed for " + count + " times. It will be retried in " + conCreationRetryInterval_ + " ms.", ex); try { Thread.sleep(conCreationRetryInterval_); } catch (InterruptedException ie) { // ignore this exception } } } } /** * Create specified number of resources using the given resource allocater. * * @param alloc the resource allocator to be used to create the new resource * @param size number of resources to create. * @throws PoolingException When unable to create a resource */ private void createResources(ResourceAllocator alloc, int size) throws PoolingException { for (int i = 0; i < size; i++) { createResourceAndAddToPool(alloc); } } @Override public void setPoolLifeCycleListener(PoolLifeCycleListener listener) { this.poolLifeCycleListener = listener; } @Override public void removePoolLifeCycleListener() { poolLifeCycleListener = null; } @Override public void deleteResource(ResourceHandle resourceHandle) { try { resourceHandle.getResourceAllocator().destroyResource(resourceHandle); } catch (Exception ex) { LOG.log(WARNING, "Unexpected exception while destroying resource from pool " + poolInfo.getName(), ex); } finally { // if connection leak tracing is running on connection being // destroyed due to error, then stop it if (resourceHandle.getResourceState().isBusy()) { leakDetector.stopConnectionLeakTracing(resourceHandle, this); } if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionDestroyed(resourceHandle.getId()); if (resourceHandle.getResourceState().isBusy()) { // Destroying a Connection due to error poolLifeCycleListener.decrementConnectionUsed(resourceHandle.getId()); if (!resourceHandle.isMarkedForReclaim()) { // If a connection is not reclaimed (in case of a reconfig) // increment numConnFree poolLifeCycleListener.incrementNumConnFree(true, steadyPoolSize); } } else { // Destroying a free Connection poolLifeCycleListener.decrementNumConnFree(); } } } } @Override public void resourceClosed(ResourceHandle handle) throws IllegalStateException { LOG.log(FINE, "Resource was closed, processing handle: {0}", handle); ResourceState state = getResourceState(handle); if (state == null) { throw new IllegalStateException("State is null"); } if (!state.isBusy()) { // Do not throw an exception, the current transaction should not fail if the state is already 'free'. LOG.log(WARNING, "resourceClosed - Expecting 'state.isBusy(): false', but was true for handle: {0}", handle); } // mark as not busy setResourceStateToFree(handle); state.touchTimestamp(); if (state.isUnenlisted() || (poolTxHelper.isNonXAResource(handle) && poolTxHelper.isLocalTransactionInProgress() && poolTxHelper.isLocalResourceEligibleForReuse(handle))) { // Note: the call to isLocalResourceEligibleForReuse can change the enlisted state of the // handle to false if the resource is eligible for reuse. freeUnenlistedResource(handle); } if (poolLifeCycleListener != null && !handle.getDestroyByLeakTimeOut()) { poolLifeCycleListener.connectionReleased(handle.getId()); } // Note handle might already be altered by another thread before it is logged! LOG.log(FINE, "Resource was freed after its closure: {0}", handle); } /** * If the resource is used for maxConnectionUsage times, destroy and create one * * @param handle Resource to be checked */ protected void performMaxConnectionUsageOperation(ResourceHandle handle) { dataStructure.removeResource(handle); LOG.log(INFO, "Destroying connection {0} since it has reached the maximum usage of: {1}", new Object[] {handle.getId(), handle.getUsageCount()}); if (poolLifeCycleListener != null) { poolLifeCycleListener.decrementConnectionUsed(handle.getId()); } // compensate with a new resource only when the pool-size is less than steady-pool-size if (dataStructure.getResourcesSize() < steadyPoolSize) { try { createResourceAndAddToPool(handle.getResourceAllocator()); } catch (Exception e) { LOG.log(WARNING, "Unable to create a new resource.", e); } } } protected void freeUnenlistedResource(ResourceHandle h) { freeResource(h); } protected void freeResource(ResourceHandle resourceHandle) { LOG.log(FINE, "freeResource handle: {0}", resourceHandle); try { getResourceFromPoolAndFreeResourceMethodsLock.lock(); if (cleanupResource(resourceHandle)) { // Only when resource handle usage count is more than maxConnUsage if (maxConnectionUsage_ > 0 && resourceHandle.getUsageCount() >= maxConnectionUsage_) { // Remove the resource handle from the pool and update the monitoring data performMaxConnectionUsageOperation(resourceHandle); } else { // Put it back to the free collection. dataStructure.returnResource(resourceHandle); // update the monitoring data if (poolLifeCycleListener != null && !resourceHandle.getDestroyByLeakTimeOut()) { poolLifeCycleListener.decrementConnectionUsed(resourceHandle.getId()); poolLifeCycleListener.incrementNumConnFree(false, steadyPoolSize); } } // For both the cases of free.add and maxConUsageOperation, a free resource is added. // Hence notify waiting threads. notifyWaitingThreads(); } } finally { getResourceFromPoolAndFreeResourceMethodsLock.unlock(); } } /** * Asks the resource allocator to cleanup the resource * * @param resource the resource to be cleaned up * @return true if cleanup was successful, otherwise false */ protected boolean cleanupResource(ResourceHandle resource) { boolean cleanupSuccessful = true; // cleanup resource try { ResourceAllocator alloc = resource.getResourceAllocator(); alloc.cleanup(resource); } catch (PoolingException ex) { LOG.log(WARNING, "Cleanup of a resource from pool [" + poolInfo.getName() + "] failed.", ex); cleanupSuccessful = false; resourceErrorOccurred(resource); } return cleanupSuccessful; } @Override public void resourceErrorOccurred(ResourceHandle resourceHandle) throws IllegalStateException { LOG.log(FINE, "Resource error occured: {0}", resourceHandle); if (failAllConnections) { // TODO: leakDetector is not updated and isBusy state of this resource is not updated correctly: possible bug. // leakDetector should be updated in the doFailAllConnectionsProcessing method. The resource can be updated here. doFailAllConnectionsProcessing(); return; } ResourceState state = getResourceState(resourceHandle); // Normally a connection error is expected // to occur only when the connection is in use by the application. // When there is a connection validation involved, the connection // can be checked for validity "before" it is passed to the // application i.e. when the resource is still free. Since, // the connection error can occur when the resource // is free, the state could still be 'isBusy: false', therefore // no exception is thrown based on the isBusy state, and only // if the state object is missing. if (state == null) { throw new IllegalStateException(); } // Mark as not busy. Even if it is removed from the Pool datastructure, // it is good to clean it up, at least to clean up the leakDetector. setResourceStateToFree(resourceHandle); state.touchTimestamp(); // changed order of commands // Commenting resources.remove() out since we will call an iter.remove() // in the getUnenlistedResource method in the if check after // matchManagedConnections or in the internalGetResource method // If we were to call remove directly here there is always the danger // of a ConcurrentModificationExceptionbeing thrown when we return // // In case of this method being called asynchronously, since // the resource has been marked as "errorOccured", it will get // removed in the next iteration of getUnenlistedResource // or internalGetResource dataStructure.removeResource(resourceHandle); // Removing a resource, means a free resource is available for the pool. // Hence notify waiting threads. notifyWaitingThreads(); } private void doFailAllConnectionsProcessing() { LOG.log(FINE, "doFailAllConnectionsProcessing()"); cancelResizerTask(); if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionValidationFailed(dataStructure.getResourcesSize()); } emptyPool(); // TODO: leakDetector might have been used and it is not cleaned up. // should call leakDetector.reset try { createResources(allocator, steadyPoolSize); LOG.log(FINE, "Successfully created new resources."); } catch (PoolingException pe) { // Ignore and hope the resizer does its stuff LOG.log(FINE, "Could not create " + steadyPoolSize + " resources.", pe); } scheduleResizerTask(); } @Override public void resourceEnlisted(Transaction tran, ResourceHandle resource) throws IllegalStateException { poolTxHelper.resourceEnlisted(tran, resource); } /** * This method is called when transaction tran is completed. * * @param tran Transaction * @param status status of transaction */ @Override public void transactionCompleted(Transaction tran, int status) throws IllegalStateException { // transactionCompleted will update all relevant resource handles to be no longer enlisted List delistedResources = poolTxHelper.transactionCompleted(tran, status, poolInfo); for (ResourceHandle resource : delistedResources) { // Application might not have closed the connection, free the resource only if it is not in use anymore. if (isResourceUnused(resource)) { freeResource(resource); // Resource is now returned to the pool and another thread can use it, cannot log it anymore. } else { // TODO: Why would the application not close a busy connection if the transaction completed and the resource handle is // delisted from the transaction? Is this done to leave the resource as used in the connection pool and let it // time out and be cleaned up by the timer? // The poolTxHelper.transactionCompleted already altered the enlisted state to be no longer enlisted in any transaction. // So this resource is no longer part of an outer transaction for example. // Would expect a warning here in case the resource handle state still marked as busy. } } } protected boolean isResourceUnused(ResourceHandle h) { return h.getResourceState().isFree(); } @Override public ResourceHandle createResource(ResourceAllocator alloc) throws PoolingException { // NOTE : Pool should not call this method directly, it should be called only by pool-datastructure ResourceHandle result = createSingleResource(alloc); result.getResourceState().reset(); if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionCreated(); } return result; } @Override public void createResourceAndAddToPool() throws PoolingException { createResourceAndAddToPool(allocator); } @Override public Set getInvalidConnections(Set connections) throws ResourceException { return allocator.getInvalidConnections(connections); } @Override public void invalidConnectionDetected(ResourceHandle h) { incrementNumConnFailedValidation(); } @Override public void resizePool(boolean forced) { resizerTask.resizePool(forced); } protected void notifyWaitingThreads() { // notify the first thread in the waitqueue Object waitMonitor = null; synchronized (waitQueue) { if (waitQueue.getQueueLength() > 0) { waitMonitor = waitQueue.remove(); if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionRequestDequeued(); } } } if (waitMonitor == null) { LOG.log(FINE, "Wait monitor is null"); } else { synchronized (waitMonitor) { LOG.log(FINE, "Notifying wait monitor: {0}", waitMonitor); waitMonitor.notifyAll(); } } } private void incrementNumConnFailedValidation() { if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionValidationFailed(1); } } // TODO: this method name should be createAndGetNewResource, but it is only used once, to it could be removed private ResourceHandle getNewResource(ResourceAllocator alloc) throws PoolingException { addResource(alloc); return dataStructure.getResource(); } private ResourceState getResourceState(ResourceHandle h) { return h.getResourceState(); } @Override public void emptyPool() { LOG.log(FINE, "Emptying pool {0}", poolInfo.getName()); dataStructure.removeAll(); } @Override public void emptyFreeConnectionsInPool() { LOG.log(FINE, "Emptying free connections in the pool {0}", poolInfo.getName()); // TODO this is not completely thread safe, between getResource and removeResource // the dataStructure can be altered by other threads ResourceHandle h; while ((h = dataStructure.getResource()) != null) { dataStructure.removeResource(h); } } @Override public String toString() { StringBuilder sb = new StringBuilder("Pool ["); sb.append(poolInfo); sb.append("] PoolSize="); sb.append(dataStructure.getResourcesSize()); sb.append(" FreeResources="); sb.append(dataStructure.getFreeListSize()); sb.append(" QueueSize="); sb.append(waitQueue.getQueueLength()); sb.append(" matching="); sb.append((matchConnections ? "on" : "off")); sb.append(" validation="); sb.append((connectionValidationRequired ? "on" : "off")); return sb.toString(); } @Override public void blockRequests(long waitTimeout) { blocked = true; this.reconfigWaitTime = waitTimeout; } @Override public PoolWaitQueue getPoolWaitQueue() { return waitQueue; } @Override public PoolWaitQueue getReconfigWaitQueue() { return reconfigWaitQueue; } @Override public long getReconfigWaitTime() { return reconfigWaitTime; } @Override public synchronized boolean flushConnectionPool() throws PoolingException { LOG.log(FINE, "Flushing Connection Pool {0}", poolInfo); if (!poolInitialized) { throw new PoolingException( "Flush Connection Pool did not happen as pool " + poolInfo + " is not initialized"); } cancelResizerTask(); dataStructure.removeAll(); scheduleResizerTask(); increaseSteadyPoolSize(steadyPoolSize); LOG.log(FINE, "Flush Connection Pool done"); return true; } @Override public synchronized void reconfigurePool(ConnectorConnectionPool poolResource) throws PoolingException { int _idleTime = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) * 1000; if (poolInitialized) { if (_idleTime != idletime && _idleTime != 0) { idletime = _idleTime; scheduleResizerTask(); } if (_idleTime == 0) { // resizerTask.cancel(); cancelResizerTask(); } } idletime = _idleTime; resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity()); maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis()); // Make sure it's not negative. if (maxWaitTime < 0) { maxWaitTime = 0; } connectionValidationRequired = poolResource.isIsConnectionValidationRequired(); failAllConnections = poolResource.isFailAllConnections(); setAdvancedPoolConfiguration(poolResource); // Self managed quantities. These are ignored if self management // is on if (!isSelfManaged()) { int _maxPoolSize = Integer.parseInt(poolResource.getMaxPoolSize()); int oldMaxPoolSize = maxPoolSize; if (_maxPoolSize < steadyPoolSize) { // should not happen, admin must throw exception when this condition happens. // as a precaution set max pool size to steady pool size maxPoolSize = steadyPoolSize; } else { maxPoolSize = _maxPoolSize; } if (oldMaxPoolSize != maxPoolSize) { dataStructure.setMaxSize(maxPoolSize); } int _steadyPoolSize = Integer.parseInt(poolResource.getSteadyPoolSize()); int oldSteadyPoolSize = steadyPoolSize; if (_steadyPoolSize > maxPoolSize) { // should not happen, admin must throw exception when this condition happens. // as a precaution set steady pool size to max pool size steadyPoolSize = maxPoolSize; } else { steadyPoolSize = _steadyPoolSize; } if (poolInitialized) { // In this case we need to kill extra connections in the pool // For the case where the value is increased, we need not // do anything // num resources to kill is decided by the resources in the pool. // if we have less than current maxPoolSize resources, we need to // kill less. int toKill = dataStructure.getResourcesSize() - maxPoolSize; if (toKill > 0) { killExtraResources(toKill); } } reconfigureSteadyPoolSize(oldSteadyPoolSize, _steadyPoolSize); } } protected void reconfigureSteadyPoolSize(int oldSteadyPoolSize, int newSteadyPoolSize) throws PoolingException { if (oldSteadyPoolSize != steadyPoolSize) { if (poolInitialized) { if (oldSteadyPoolSize < steadyPoolSize) { increaseSteadyPoolSize(newSteadyPoolSize); if (poolLifeCycleListener != null) { poolLifeCycleListener.connectionsFreed(steadyPoolSize); } } } } } /** * sets advanced pool properties
* used during pool configuration (initialization) and re-configuration
* * @param poolResource Connector Connection Pool */ private void setAdvancedPoolConfiguration(ConnectorConnectionPool poolResource) { matchConnections = poolResource.matchConnections(); preferValidateOverRecreate = poolResource.isPreferValidateOverRecreate(); maxConnectionUsage_ = Integer.parseInt(poolResource.getMaxConnectionUsage()); connectionCreationRetryAttempts_ = Integer.parseInt(poolResource.getConCreationRetryAttempts()); // Converting seconds to milliseconds as TimerTask will take input in milliseconds conCreationRetryInterval_ = Integer.parseInt(poolResource.getConCreationRetryInterval()) * 1000L; connectionCreationRetry_ = connectionCreationRetryAttempts_ > 0; validateAtmostPeriodInMilliSeconds_ = Integer.parseInt(poolResource.getValidateAtmostOncePeriod()) * 1000L; boolean connectionLeakReclaim_ = poolResource.isConnectionReclaim(); long connectionLeakTimeoutInMilliSeconds_ = Integer.parseInt(poolResource.getConnectionLeakTracingTimeout()) * 1000L; boolean connectionLeakTracing_ = connectionLeakTimeoutInMilliSeconds_ > 0; if (leakDetector == null) { leakDetector = new ConnectionLeakDetector(poolInfo, connectionLeakTracing_, connectionLeakTimeoutInMilliSeconds_, connectionLeakReclaim_); } else { leakDetector.reset(connectionLeakTracing_, connectionLeakTimeoutInMilliSeconds_, connectionLeakReclaim_); } } /** * Kill the extra resources.
* The maxPoolSize being reduced causes this method to be called */ private void killExtraResources(int numToKill) { cancelResizerTask(); ResourceHandle h; for (int i = 0; i < numToKill && ((h = dataStructure.getResource()) != null); i++) { dataStructure.removeResource(h); } scheduleResizerTask(); } /** * Increase the number of steady resources in the pool if we detect that the steadyPoolSize has been increased.
* Note: if the newSteadyPoolSize is smaller than the current pool size, no changes are made directly. The resizer task * is updated in all cases and will resize the pool in the future. * * @param newSteadyPoolSize The new steady pool size * @throws PoolingException when unable to add new resources to the pool */ private void increaseSteadyPoolSize(int newSteadyPoolSize) throws PoolingException { cancelResizerTask(); for (int i = dataStructure.getResourcesSize(); i < newSteadyPoolSize; i++) { createResourceAndAddToPool(allocator); } scheduleResizerTask(); } // TODO is this private method needed? Why not call addResource directly? private void createResourceAndAddToPool(ResourceAllocator alloc) throws PoolingException { addResource(alloc); } @Override public void switchOnMatching() { matchConnections = true; } @Override public final PoolInfo getPoolInfo() { return poolInfo; } @Override public synchronized void cancelResizerTask() { LOG.log(FINE, "Cancelling resizer task."); if (resizerTask != null) { resizerTask.cancel(); } resizerTask = null; if (resizerTaskTimer != null) { resizerTaskTimer.purge(); } } // Self management methods @Override public int getMaxPoolSize() { return maxPoolSize; } @Override public int getResizeQuantity() { return resizeQuantity; } @Override public long getIdleTimeout() { return idletime; } @Override public int getWaitQueueLength() { return waitQueue.getQueueLength(); } @Override public int getSteadyPoolSize() { return steadyPoolSize; } @Override public void setMaxPoolSize(int size) { if (size < dataStructure.getResourcesSize()) { synchronized (this) { int toKill = dataStructure.getResourcesSize() - size; if (toKill > 0) { try { killExtraResources(toKill); } catch (Exception re) { // ignore for now LOG.log(FINE, "setMaxPoolSize:: killExtraResources throws exception!", re); } } } } maxPoolSize = size; } @Override public void setSteadyPoolSize(int size) { steadyPoolSize = size; } @Override public void setSelfManaged(boolean selfManaged) { if (LOG.isLoggable(FINE)) { LOG.log(FINE, "Setting selfManaged to: " + selfManaged + " in pool: " + poolInfo.getName()); } selfManaged_ = selfManaged; } protected boolean isSelfManaged() { return selfManaged_; } @Override public void potentialConnectionLeakFound() { if (poolLifeCycleListener != null) { poolLifeCycleListener.foundPotentialConnectionLeak(); } } @Override public void printConnectionLeakTrace(StringBuffer stackTrace) { if (poolLifeCycleListener != null) { stackTrace.append('\n'); stackTrace.append("Monitoring Statistics: "); stackTrace.append('\n'); poolLifeCycleListener.toString(stackTrace); } } @Override public void reclaimConnection(ResourceHandle handle) { // all reclaimed connections must be killed instead of returning them to the pool. // Entity beans when used in bean managed transaction will face an issue since connections // are destroyed during reclaim. // Stateful session beans will work fine. LOG.log(INFO, "Reclaiming the leaked connection of pool [{0}] and destroying it so as to avoid both" + " the application that leaked the connection and any other request that can potentially acquire" + " the same connection from the pool end up using the connection at the same time", poolInfo.getName()); dataStructure.removeResource(handle); handle.setDestroyByLeakTimeOut(true); notifyWaitingThreads(); } @Override public PoolStatus getPoolStatus() { PoolStatus poolStatus = new PoolStatus(this.poolInfo); int numFree = this.poolInitialized ? dataStructure.getFreeListSize() : 0; int numUsed = this.poolInitialized ? dataStructure.getResourcesSize() - dataStructure.getFreeListSize() : 0; poolStatus.setNumConnFree(numFree); poolStatus.setNumConnUsed(numUsed); return poolStatus; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy