| /* |
| * 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.appserv.connectors.internal.spi.BadConnectionEventListener; |
| import com.sun.enterprise.connectors.ConnectorConnectionPool; |
| import com.sun.enterprise.connectors.ConnectorRuntime; |
| import com.sun.enterprise.connectors.service.ConnectorAdminServiceUtils; |
| 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.enterprise.util.i18n.StringManager; |
| import com.sun.logging.LogDomains; |
| import org.glassfish.resourcebase.resources.api.PoolInfo; |
| |
| import javax.naming.NamingException; |
| import jakarta.resource.ResourceException; |
| import jakarta.resource.spi.RetryableUnavailableException; |
| import jakarta.transaction.Transaction; |
| import java.util.*; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Connection Pool for Connector & JDBC resources<br> |
| * |
| * @author Jagadish Ramu |
| */ |
| public class ConnectionPool implements ResourcePool, ConnectionLeakListener, |
| ResourceHandler, PoolProperties { |
| |
| protected final static StringManager localStrings = |
| StringManager.getManager(ConnectionPool.class); |
| protected final static Logger _logger = LogDomains.getLogger(ConnectionPool.class,LogDomains.RSR_LOGGER); |
| |
| |
| //pool life-cycle config properties |
| protected int maxPoolSize; // Max size of the pool |
| protected int steadyPoolSize; // Steady size of the pool |
| protected int resizeQuantity; // used by resizer to downsize the pool |
| protected int maxWaitTime; // The total time a thread is willing to wait for a resource object. |
| protected long idletime; // time (in ms) before destroying a free resource |
| |
| |
| //pool config properties |
| protected boolean failAllConnections = false; |
| protected boolean matchConnections = false; |
| protected boolean validation = false; |
| protected boolean preferValidateOverRecreate = false; |
| // hold on to the resizer task so we can cancel/reschedule it. |
| protected Resizer resizerTask; |
| |
| |
| protected volatile boolean poolInitialized = false; |
| protected Timer timer; |
| |
| //advanced pool config properties |
| protected boolean connectionCreationRetry_; |
| protected int connectionCreationRetryAttempts_; |
| protected long conCreationRetryInterval_; |
| protected long validateAtmostPeriodInMilliSeconds_; |
| protected int maxConnectionUsage_; |
| //To validate a Sun RA Pool Connection if it hasnot been validated |
| // in the past x sec. (x=idle-timeout) |
| //The property will be set from system property - |
| // com.sun.enterprise.connectors.ValidateAtmostEveryIdleSecs=true |
| private boolean validateAtmostEveryIdleSecs = false; |
| |
| protected String resourceSelectionStrategyClass; |
| |
| 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; |
| |
| protected DataStructure ds; |
| protected String dataStructureType; |
| protected String dataStructureParameters; |
| |
| protected PoolWaitQueue waitQueue; |
| protected PoolWaitQueue reconfigWaitQueue; |
| private long reconfigWaitTime ; |
| protected String poolWaitQueueClass; |
| |
| protected final PoolInfo poolInfo; //poolName |
| |
| private PoolTxHelper poolTxHelper; |
| |
| // NOTE: This resource allocator may not be the same as the allocator passed in to getResource() |
| protected ResourceAllocator allocator; |
| |
| private boolean selfManaged_; |
| |
| private boolean blocked = false; |
| |
| |
| 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); |
| if(_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Connection Pool : " + this.poolInfo); |
| } |
| } |
| |
| protected void initializePoolWaitQueue() throws PoolingException { |
| waitQueue = PoolWaitQueueFactory.createPoolWaitQueue(poolWaitQueueClass); |
| reconfigWaitQueue = PoolWaitQueueFactory.createPoolWaitQueue(poolWaitQueueClass); |
| } |
| |
| protected void initializePoolDataStructure() throws PoolingException { |
| ds = DataStructureFactory.getDataStructure(dataStructureType, dataStructureParameters, |
| maxPoolSize, this, resourceSelectionStrategyClass); |
| } |
| |
| 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(); |
| |
| validation = poolResource.isIsConnectionValidationRequired(); |
| |
| validateAtmostEveryIdleSecs = poolResource.isValidateAtmostEveryIdleSecs(); |
| dataStructureType = poolResource.getPoolDataStructureType(); |
| dataStructureParameters = poolResource.getDataStructureParameters(); |
| poolWaitQueueClass = poolResource.getPoolWaitQueue(); |
| resourceSelectionStrategyClass = poolResource.getResourceSelectionStrategyClass(); |
| resourceGatewayClass = poolResource.getResourceGatewayClass(); |
| reconfigWaitTime = poolResource.getDynamicReconfigWaitTimeout(); |
| |
| setAdvancedPoolConfiguration(poolResource); |
| } |
| |
| protected ConnectorConnectionPool getPoolConfigurationFromJndi(Hashtable env) throws PoolingException { |
| ConnectorConnectionPool poolResource; |
| try { |
| String jndiNameOfPool = ConnectorAdminServiceUtils.getReservePrefixedJNDINameForPool(poolInfo); |
| poolResource = (ConnectorConnectionPool) |
| ConnectorRuntime.getRuntime().getResourceNamingService().lookup(poolInfo, jndiNameOfPool, env); |
| } catch (NamingException ex) { |
| throw new PoolingException(ex); |
| } |
| return poolResource; |
| } |
| |
| // 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 - ds.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 canceled and a new one is scheduled. |
| */ |
| private void scheduleResizerTask() { |
| if (resizerTask != null) { |
| //cancel the current task |
| resizerTask.cancel(); |
| resizerTask = null; |
| } |
| |
| resizerTask = initializeResizer(); |
| |
| if (timer == null) { |
| timer = ConnectorRuntime.getRuntime().getTimer(); |
| } |
| |
| timer.scheduleAtFixedRate(resizerTask, idletime, idletime); |
| if (_logger.isLoggable(Level.FINEST)) { |
| _logger.finest("scheduled resizer task"); |
| } |
| } |
| |
| protected Resizer initializeResizer() { |
| return new Resizer(poolInfo, ds, this, this, preferValidateOverRecreate); |
| } |
| |
| /** |
| * add a resource with status busy and not enlisted |
| * |
| * @param alloc ResourceAllocator |
| * @throws PoolingException when unable to add a resource |
| */ |
| public void addResource(ResourceAllocator alloc) throws PoolingException { |
| int numResCreated = ds.addResource(alloc, 1); |
| if(numResCreated > 0) { |
| for(int i=0; i< numResCreated; i++) { |
| if(poolLifeCycleListener != null) { |
| poolLifeCycleListener.incrementNumConnFree(false, steadyPoolSize); |
| } |
| } |
| } |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.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 resource from the pool. |
| * |
| * @return a free pooled resource object matching the ResourceSpec |
| * @throws PoolingException - if any error occurrs |
| * - or the pool has reached its max size and the |
| * max-connection-wait-time-in-millis has expired. |
| */ |
| public ResourceHandle getResource(ResourceSpec spec, ResourceAllocator alloc, Transaction txn) |
| 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 jtx = ((JavaEETransaction) txn); |
| Set resourcesSet = null; |
| if(jtx != null){ |
| resourcesSet = jtx.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.size() > 0)) { |
| try { |
| result = internalGetResource(spec, alloc, txn); |
| } 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 (_logger.isLoggable( Level.FINE) ) { |
| _logger.log(Level.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; |
| } else { |
| // 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(); |
| } |
| String msg = localStrings.getStringWithDefault( |
| "poolmgr.no.available.resource", |
| "No available resource. Wait-time expired."); |
| throw new PoolingException(msg); |
| } |
| } |
| } |
| |
| if (!blocked) { |
| //add to wait-queue |
| Object waitMonitor = new Object(); |
| if (poolLifeCycleListener != null) { |
| poolLifeCycleListener.connectionRequestQueued(); |
| } |
| synchronized (waitMonitor) { |
| waitQueue.addToQueue(waitMonitor); |
| |
| try { |
| logFine("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 dont expect the queue to grow to great numbers |
| // so the overhead for removing inexistant objects is low. |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "removing wait monitor from queue: " + 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){ |
| if(_logger.isLoggable(Level.FINEST)) { |
| _logger.finest("[DRC] getting into reconfig wait queue for time ["+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. |
| if(_logger.isLoggable(Level.FINEST)) { |
| _logger.log(Level.FINEST, "[DRC] removing wait monitor from reconfig-wait-queue: " + |
| reconfigWaitMonitor); |
| } |
| |
| reconfigWaitQueue.removeFromQueue(reconfigWaitMonitor); |
| |
| if(_logger.isLoggable(Level.FINEST)) { |
| _logger.log(Level.FINEST, "[DRC] throwing Retryable-Unavailable-Exception"); |
| } |
| RetryableUnavailableException rue = new RetryableUnavailableException("Pool Reconfigured, " + |
| "Connection Factory can retry the lookup"); |
| rue.setErrorCode(BadConnectionEventListener.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 ResourceSpec |
| * @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 spec, |
| ResourceAllocator alloc, |
| Transaction tran) throws PoolingException { |
| if (!poolInitialized) { |
| initPool(alloc); |
| } |
| ResourceHandle result; |
| |
| result = getResourceFromTransaction(tran, alloc, spec); |
| if(result != null){ |
| return result; |
| } |
| |
| result = prefetch(spec, alloc, tran); |
| if (result != null) { |
| return result; |
| } |
| |
| // We didnt get a connection that is already enlisted in the current transaction (if any). |
| result = getUnenlistedResource(spec, alloc, tran); |
| if (result != null) { |
| if (maxConnectionUsage_ > 0) { |
| result.incrementUsageCount(); |
| } |
| if (poolLifeCycleListener != null) { |
| poolLifeCycleListener.connectionUsed(result.getId()); |
| //Decrement numConnFree |
| poolLifeCycleListener.decrementNumConnFree(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Try to get a resource from current transaction if it is shareable<br> |
| * @param tran Current Transaction |
| * @param alloc ResourceAllocator |
| * @param spec ResourceSpec |
| * @return result ResourceHandle |
| */ |
| private ResourceHandle getResourceFromTransaction(Transaction tran, ResourceAllocator alloc, ResourceSpec spec) { |
| ResourceHandle result = null; |
| try { |
| //comment-1: sharing is possible only if caller is marked |
| //shareable, so abort right here if that's not the case |
| if (tran != null && alloc.shareableWithinComponent()) { |
| //TODO should be handled by PoolTxHelper |
| JavaEETransaction j2eetran = (JavaEETransaction) tran; |
| // case 1. look for free and enlisted in same tx |
| Set set = j2eetran.getResources(poolInfo); |
| if (set != null) { |
| Iterator iter = set.iterator(); |
| while (iter.hasNext()) { |
| ResourceHandle h = (ResourceHandle) iter.next(); |
| if (h.hasConnectionErrorOccurred()) { |
| iter.remove(); |
| continue; |
| } |
| |
| ResourceState state = h.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 (h.getResourceAllocator().shareableWithinComponent()) { |
| if (spec.isXA() || poolTxHelper.isNonXAResourceAndFree(j2eetran, h)) { |
| if (matchConnections) { |
| if (!alloc.matchConnection(h)) { |
| if (poolLifeCycleListener != null) { |
| poolLifeCycleListener.connectionNotMatched(); |
| } |
| continue; |
| } |
| if (h.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 |
| result = null; |
| break; |
| } |
| iter.remove(); |
| continue; |
| } |
| if (poolLifeCycleListener != null) { |
| poolLifeCycleListener.connectionMatched(); |
| } |
| } |
| if (state.isFree()) |
| setResourceStateToBusy(h); |
| result = h; |
| break; |
| } |
| } |
| } |
| } |
| } |
| } catch (ClassCastException e) { |
| if(_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Pool: getResource : " + |
| "transaction is not JavaEETransaction but a " + tran.getClass().getName(), e); |
| } |
| } |
| return result; |
| } |
| |
| |
| /** |
| * To provide an unenlisted, valid, matched resource from pool. |
| * |
| * @param spec ResourceSpec |
| * @param alloc ResourceAllocator |
| * @param tran Transaction |
| * @return ResourceHandle resource from pool |
| * @throws PoolingException Exception while getting resource from pool |
| */ |
| protected ResourceHandle getUnenlistedResource(ResourceSpec spec, ResourceAllocator alloc, |
| Transaction tran) throws PoolingException { |
| return getResourceFromPool(alloc, spec); |
| } |
| |
| /** |
| * Check whether the connection is valid |
| * |
| * @param h Resource to be validated |
| * @param alloc Allocator to validate the resource |
| * @return boolean representing validation result |
| */ |
| protected boolean isConnectionValid(ResourceHandle h, ResourceAllocator alloc) { |
| boolean connectionValid = true; |
| |
| if (validation || validateAtmostEveryIdleSecs) { |
| long validationPeriod; |
| //validation period is idle timeout if validateAtmostEveryIdleSecs is set to true |
| //else it is validateAtmostPeriodInMilliSeconds_ |
| if (validation) |
| validationPeriod = validateAtmostPeriodInMilliSeconds_; |
| else |
| validationPeriod = idletime; |
| boolean validationRequired = true; |
| long currentTime = h.getLastValidated(); |
| if (validationPeriod > 0) { |
| currentTime = System.currentTimeMillis(); |
| long timeSinceValidation = currentTime - h.getLastValidated(); |
| if (timeSinceValidation < validationPeriod) |
| validationRequired = false; |
| } |
| if (validationRequired) { |
| if (!alloc.isConnectionValid(h)) { |
| connectionValid = false; |
| incrementNumConnFailedValidation(); |
| } else { |
| h.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) { |
| 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 <br> |
| * return a new resource. returns null if the pool new resources cannot be created. <br> |
| * |
| * @param alloc ResourceAllocator |
| * @return ResourceHandle resource from pool |
| * @throws PoolingException if unable to create a new resource |
| */ |
| protected ResourceHandle getResourceFromPool(ResourceAllocator alloc, ResourceSpec spec) |
| 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 result = null; |
| |
| ResourceHandle h; |
| ArrayList<ResourceHandle> freeResources = new ArrayList<ResourceHandle>(); |
| try{ |
| while ((h = ds.getResource()) != null) { |
| |
| if (h.hasConnectionErrorOccurred()) { |
| ds.removeResource(h); |
| continue; |
| } |
| |
| if (matchConnection(h, alloc)) { |
| |
| boolean isValid = isConnectionValid(h, alloc); |
| if (h.hasConnectionErrorOccurred() || !isValid) { |
| if (failAllConnections) { |
| result = createSingleResourceAndAdjustPool(alloc, spec); |
| //no need to match since the resource is created with the allocator of caller. |
| break; |
| } else { |
| ds.removeResource(h); |
| //resource is invalid, continue iteration. |
| continue; |
| } |
| } |
| if(h.isShareable() == alloc.shareableWithinComponent()){ |
| // got a matched, valid resource |
| result = h; |
| break; |
| }else{ |
| freeResources.add(h); |
| } |
| } else { |
| freeResources.add(h); |
| } |
| } |
| }finally{ |
| //return all unmatched, free resources |
| for (ResourceHandle freeResource : freeResources) { |
| ds.returnResource(freeResource); |
| } |
| freeResources.clear(); |
| } |
| |
| if (result != null) { |
| // set correct state |
| setResourceStateToBusy(result); |
| } else { |
| result = resizePoolAndGetNewResource(alloc); |
| } |
| return result; |
| } |
| |
| /** |
| * Scale-up the pool to serve the new request. <br> |
| * If pool is at max-pool-size and free resources are found, purge unmatched<br> |
| * resources, create new connections and serve the request.<br> |
| * |
| * @param alloc ResourceAllocator used to create new resources |
| * @return ResourceHandle newly created resource |
| * @throws PoolingException when not able to create resources |
| */ |
| private ResourceHandle resizePoolAndGetNewResource(ResourceAllocator alloc) throws PoolingException { |
| //Must be called from the thread holding the lock to this pool. |
| ResourceHandle result = null; |
| int numOfConnsToCreate = 0; |
| if (ds.getResourcesSize() < 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 - ds.getResourcesSize(); |
| } else if (ds.getResourcesSize() + resizeQuantity <= maxPoolSize) { |
| //Create and add resources of quantity "resizeQuantity" |
| numOfConnsToCreate = resizeQuantity; |
| } else if (ds.getResourcesSize() < 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 - ds.getResourcesSize(); |
| } |
| if (numOfConnsToCreate > 0) { |
| createResources(alloc, numOfConnsToCreate); |
| result = getMatchedResourceFromPool(alloc); |
| } else if (ds.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) { |
| result = resizePoolAndGetNewResource(alloc); |
| } |
| } |
| return result; |
| } |
| |
| //TODO can't this be replaced by getResourceFromPool ? |
| private ResourceHandle getMatchedResourceFromPool(ResourceAllocator alloc) { |
| ResourceHandle handle; |
| ResourceHandle result = null; |
| ArrayList<ResourceHandle> activeResources = new ArrayList<ResourceHandle>(); |
| |
| try{ |
| while ((handle = ds.getResource()) != null) { |
| if (matchConnection(handle, alloc)) { |
| result = handle; |
| setResourceStateToBusy(result); |
| break; |
| } else { |
| activeResources.add(handle); |
| } |
| } |
| }finally{ |
| //return unmatched resources |
| for (ResourceHandle activeResource : activeResources) { |
| ds.returnResource(activeResource); |
| } |
| activeResources.clear(); |
| } |
| return result; |
| } |
| |
| /** |
| * Try to purge resources by size <= quantity <br> |
| * |
| * @param quantity maximum no. of resources to remove. <br> |
| * @return resourceCount No. of resources actually removed. <br> |
| */ |
| private int purgeResources(int quantity) { |
| //Must be called from the thread holding the lock to this pool. |
| int totalResourcesRemoved = 0; |
| int freeResourcesCount = ds.getFreeListSize(); |
| int resourcesCount = (freeResourcesCount >= quantity) ? |
| quantity : freeResourcesCount; |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Purging resources of size : " + resourcesCount); |
| } |
| for (int i = resourcesCount - 1; i >= 0; i--) { |
| |
| ResourceHandle resource = ds.getResource(); |
| if (resource != null) { |
| ds.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 alloc ResourceAllocator to create resource |
| * @param spec ResourceSpec |
| * @return newly created resource |
| * @throws PoolingException when unable to create a resource |
| */ |
| protected ResourceHandle createSingleResourceAndAdjustPool( |
| ResourceAllocator alloc, ResourceSpec spec) |
| throws PoolingException { |
| |
| ResourceHandle handle = ds.getResource(); |
| if (handle != null) { |
| ds.removeResource(handle); |
| } |
| |
| ResourceHandle result = getNewResource(alloc); |
| |
| return result; |
| } |
| |
| |
| /** |
| * Method to be used to create resource, instead of calling ResourceAllocator.createConfigBean(). |
| * This method handles the connection creation retrial in case of failure |
| * |
| * @param resourceAllocator ResourceAllocator |
| * @return ResourceHandle newly created resource |
| * @throws PoolingException when unable create a resource |
| */ |
| protected ResourceHandle createSingleResource(ResourceAllocator resourceAllocator) throws PoolingException { |
| ResourceHandle resourceHandle; |
| int count = 0; |
| long startTime = 0; |
| while (true) { |
| try { |
| count++; |
| startTime = System.currentTimeMillis(); |
| resourceHandle = resourceAllocator.createResource(); |
| if(_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Time taken to create a single " |
| + "resource : " |
| + resourceHandle.getResourceSpec().getResourceId() |
| + " and adding to the pool (ms) : " |
| + (System.currentTimeMillis() - startTime)); |
| } |
| if (validation || validateAtmostEveryIdleSecs) |
| resourceHandle.setLastValidated(System.currentTimeMillis()); |
| break; |
| } catch (Exception ex) { |
| if(_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Connection creation failed for " + count + " time. It will be retried, " |
| + "if connection creation retrial is enabled.", ex); |
| } |
| if (!connectionCreationRetry_ || count > connectionCreationRetryAttempts_) |
| throw new PoolingException(ex); |
| try { |
| Thread.sleep(conCreationRetryInterval_); |
| } catch (InterruptedException ie) { |
| //ignore this exception |
| } |
| } |
| } |
| return resourceHandle; |
| } |
| |
| /** |
| * Create specified number of resources. |
| * |
| * @param alloc ResourceAllocator |
| * @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); |
| } |
| } |
| |
| |
| public void setPoolLifeCycleListener(PoolLifeCycleListener listener) { |
| this.poolLifeCycleListener = listener; |
| } |
| |
| public void removePoolLifeCycleListener() { |
| poolLifeCycleListener = null; |
| } |
| |
| public void deleteResource(ResourceHandle resourceHandle) { |
| try { |
| resourceHandle.getResourceAllocator().destroyResource(resourceHandle); |
| } catch (Exception ex) { |
| Object[] args = new Object[] { |
| resourceHandle.getResourceSpec().getPoolInfo(), |
| ex.getMessage() == null ? "" : ex.getMessage() }; |
| _logger.log(Level.WARNING, "poolmgr.destroy_resource_failed", args); |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "poolmgr.destroy_resource_failed", 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(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * this method is called to indicate that the resource is |
| * not used by a bean/application anymore |
| */ |
| public void resourceClosed(ResourceHandle h) |
| throws IllegalStateException { |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Pool: resourceClosed: " + h); |
| } |
| |
| ResourceState state = getResourceState(h); |
| if (state == null) { |
| throw new IllegalStateException("State is null"); |
| } |
| |
| if (!state.isBusy()) { |
| throw new IllegalStateException("state.isBusy() : false"); |
| } |
| |
| setResourceStateToFree(h); // mark as not busy |
| state.touchTimestamp(); |
| |
| if (state.isUnenlisted() || (poolTxHelper.isNonXAResource(h) && |
| poolTxHelper.isLocalTransactionInProgress() |
| && poolTxHelper.isLocalResourceEligibleForReuse(h))) { |
| freeUnenlistedResource(h); |
| } |
| |
| if (poolLifeCycleListener != null && !h.getDestroyByLeakTimeOut()) { |
| poolLifeCycleListener.connectionReleased(h.getId()); |
| } |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Pool: resourceFreed: " + h); |
| } |
| } |
| |
| /** |
| * If the resource is used for <i>maxConnectionUsage</i> times, destroy and create one |
| * |
| * @param handle Resource to be checked |
| */ |
| protected void performMaxConnectionUsageOperation(ResourceHandle handle) { |
| ds.removeResource(handle); |
| _logger.log(Level.INFO, "resource_pool.remove_max_used_conn", |
| 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 (ds.getResourcesSize() < steadyPoolSize) { |
| try { |
| createResourceAndAddToPool(handle.getResourceAllocator()); |
| } catch (Exception e) { |
| _logger.log(Level.WARNING, "resource_pool.failed_creating_resource", e); |
| } |
| } |
| } |
| |
| protected void freeUnenlistedResource(ResourceHandle h) { |
| freeResource(h); |
| } |
| |
| protected void freeResource(ResourceHandle resourceHandle) { |
| if(cleanupResource(resourceHandle)) { |
| //Only when resource handle usage count is more than maxConnUsage |
| if (maxConnectionUsage_ > 0 && |
| resourceHandle.getUsageCount() >= maxConnectionUsage_) { |
| performMaxConnectionUsageOperation(resourceHandle); |
| } else { |
| // Put it back to the free collection. |
| ds.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(); |
| } |
| } |
| |
| protected boolean cleanupResource(ResourceHandle handle) { |
| boolean cleanupSuccessful = true; |
| // cleanup resource |
| try { |
| ResourceAllocator alloc = handle.getResourceAllocator(); |
| alloc.cleanup(handle); |
| } catch (PoolingException ex) { |
| Object[] params = new Object[]{poolInfo, ex}; |
| _logger.log(Level.WARNING, "cleanup.resource.failed", params); |
| cleanupSuccessful = false; |
| resourceErrorOccurred(handle); |
| } |
| return cleanupSuccessful; |
| } |
| |
| public void resourceErrorOccurred(ResourceHandle h) |
| throws IllegalStateException { |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Pool: resourceErrorOccurred: " + h); |
| } |
| |
| if (failAllConnections) { |
| doFailAllConnectionsProcessing(); |
| return; |
| } |
| |
| ResourceState state = getResourceState(h); |
| // The reason is that normally connection error is expected |
| // to occur only when the connection is in use by the application. |
| // When there is 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 following is being commented out. |
| |
| /*if (state == null || |
| state.isBusy() == false) { |
| throw new IllegalStateException(); |
| } */ |
| |
| if (state == null) { |
| throw new IllegalStateException(); |
| } |
| |
| // 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 |
| ds.removeResource(h); |
| } |
| |
| private void doFailAllConnectionsProcessing() { |
| logFine("doFailAllConnectionsProcessing entered"); |
| cancelResizerTask(); |
| if (poolLifeCycleListener != null) { |
| poolLifeCycleListener.connectionValidationFailed(ds.getResourcesSize()); |
| } |
| |
| emptyPool(); |
| try { |
| createResources(allocator, steadyPoolSize); |
| } catch (PoolingException pe) { |
| //Ignore and hope the resizer does its stuff |
| logFine("in doFailAllConnectionsProcessing couldn't create steady resources"); |
| } |
| scheduleResizerTask(); |
| logFine("doFailAllConnectionsProcessing done - created new resources"); |
| |
| } |
| |
| /** |
| * this method is called when a resource is enlisted in |
| * |
| * @param tran Transaction |
| * @param resource ResourceHandle |
| */ |
| 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 |
| */ |
| public void transactionCompleted(Transaction tran, int status) throws IllegalStateException { |
| List<ResourceHandle> delistedResources = poolTxHelper.transactionCompleted(tran, status, poolInfo); |
| for (ResourceHandle resource : delistedResources) { |
| // Application might not have closed the connection. |
| if (isResourceUnused(resource)) { |
| freeResource(resource); |
| } |
| } |
| } |
| |
| protected boolean isResourceUnused(ResourceHandle h) { |
| return h.getResourceState().isFree(); |
| } |
| |
| |
| 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); |
| |
| ResourceState state = new ResourceState(); |
| state.setBusy(false); |
| state.setEnlisted(false); |
| result.setResourceState(state); |
| |
| if (poolLifeCycleListener != null) { |
| poolLifeCycleListener.connectionCreated(); |
| } |
| return result; |
| } |
| |
| public void createResourceAndAddToPool() throws PoolingException { |
| createResourceAndAddToPool(allocator); |
| } |
| |
| public Set getInvalidConnections(Set connections) throws ResourceException { |
| return allocator.getInvalidConnections(connections); |
| } |
| |
| public void invalidConnectionDetected(ResourceHandle h) { |
| incrementNumConnFailedValidation(); |
| } |
| |
| 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) { |
| synchronized (waitMonitor) { |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Notifying wait monitor : " + waitMonitor.toString()); |
| } |
| waitMonitor.notifyAll(); |
| } |
| } else { |
| logFine(" Wait monitor is null"); |
| } |
| } |
| |
| private void incrementNumConnFailedValidation() { |
| if (poolLifeCycleListener != null) { |
| poolLifeCycleListener.connectionValidationFailed(1); |
| } |
| } |
| |
| private ResourceHandle getNewResource(ResourceAllocator alloc) throws PoolingException { |
| addResource(alloc); |
| return ds.getResource(); |
| } |
| |
| |
| private ResourceState getResourceState(ResourceHandle h) { |
| return h.getResourceState(); |
| } |
| |
| public void emptyPool() { |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "EmptyPool: Name = " + poolInfo); |
| } |
| ds.removeAll(); |
| } |
| |
| public void emptyFreeConnectionsInPool() { |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Emptying free connections in pool : " + poolInfo); |
| } |
| |
| ResourceHandle h; |
| while ((h = ds.getResource()) != null) { |
| ds.removeResource(h); |
| } |
| } |
| |
| public String toString() { |
| StringBuffer sb = new StringBuffer("Pool ["); |
| sb.append(poolInfo); |
| sb.append("] PoolSize="); |
| sb.append(ds.getResourcesSize()); |
| sb.append(" FreeResources="); |
| sb.append(ds.getFreeListSize()); |
| sb.append(" QueueSize="); |
| sb.append(waitQueue.getQueueLength()); |
| sb.append(" matching="); |
| sb.append((matchConnections ? "on" : "off")); |
| sb.append(" validation="); |
| sb.append((validation ? "on" : "off")); |
| return sb.toString(); |
| } |
| |
| /** |
| * @inheritDoc |
| */ |
| public void blockRequests(long waitTimeout){ |
| blocked = true; |
| this.reconfigWaitTime = waitTimeout; |
| } |
| |
| /** |
| * @inheritDoc |
| */ |
| public PoolWaitQueue getPoolWaitQueue(){ |
| return waitQueue; |
| } |
| |
| /** |
| * @inheritDoc |
| */ |
| public PoolWaitQueue getReconfigWaitQueue(){ |
| return reconfigWaitQueue; |
| } |
| |
| |
| public long getReconfigWaitTime() { |
| return reconfigWaitTime; |
| } |
| |
| /** |
| * Reinitialize connections established in the connection pool and |
| * bring the pool to steady pool size. |
| * |
| * @throws com.sun.appserv.connectors.internal.api.PoolingException |
| */ |
| public synchronized boolean flushConnectionPool() throws PoolingException { |
| |
| logFine("Flush Connection Pool entered"); |
| |
| if(!poolInitialized) { |
| _logger.log(Level.WARNING, "poolmgr.flush_noop_pool_not_initialized", getPoolInfo()); |
| String exString = localStrings.getString("poolmgr.flush_noop_pool_not_initialized", |
| poolInfo.toString()); |
| throw new PoolingException(exString); |
| } |
| |
| try { |
| cancelResizerTask(); |
| ds.removeAll(); |
| scheduleResizerTask(); |
| increaseSteadyPoolSize(steadyPoolSize); |
| } catch(PoolingException ex) { |
| _logger.log(Level.WARNING, "pool.flush_pool_failure", |
| new Object[] {getPoolInfo(), ex.getMessage()}); |
| throw ex; |
| } |
| logFine("Flush Connection Pool done"); |
| |
| return true; |
| } |
| |
| /** |
| * Reconfigure the Pool's properties. The reconfigConnectorConnectionPool |
| * method in the ConnectorRuntime will use this method (through PoolManager) |
| * if it needs to just change pool properties and not recreate the pool |
| * |
| * @param poolResource - the ConnectorConnectionPool JavaBean that holds |
| * the new pool properties |
| * @throws PoolingException if the pool resizing fails |
| */ |
| 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; |
| } |
| |
| validation = 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) { |
| ds.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 = ds.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<br> |
| * used during pool configuration (initialization) and re-configuration<br> |
| * |
| * @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.<br> |
| * 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 = ds.getResource()) != null); i++) { |
| ds.removeResource(h); |
| } |
| scheduleResizerTask(); |
| } |
| |
| /* |
| * Increase the number of steady resources in the pool |
| * if we detect that the steadyPoolSize has been increased |
| */ |
| private void increaseSteadyPoolSize(int newSteadyPoolSize) |
| throws PoolingException { |
| cancelResizerTask(); |
| for (int i = ds.getResourcesSize(); i < newSteadyPoolSize; i++) { |
| createResourceAndAddToPool(allocator); |
| } |
| scheduleResizerTask(); |
| } |
| |
| /** |
| * @param alloc ResourceAllocator |
| * @throws PoolingException when unable to create a resource |
| */ |
| private void createResourceAndAddToPool(ResourceAllocator alloc) throws PoolingException { |
| addResource(alloc); |
| } |
| |
| /** |
| * Switch on matching of connections in the pool. |
| */ |
| public void switchOnMatching() { |
| matchConnections = true; |
| } |
| |
| /** |
| * query the name of this pool. Required by monitoring |
| * |
| * @return the name of this pool |
| */ |
| public PoolInfo getPoolInfo() { |
| return poolInfo; |
| } |
| |
| public synchronized void cancelResizerTask() { |
| |
| logFine("Cancelling resizer"); |
| if (resizerTask != null) { |
| resizerTask.cancel(); |
| } |
| resizerTask = null; |
| |
| if (timer != null) { |
| timer.purge(); |
| } |
| } |
| |
| |
| /** |
| * This method can be used for debugging purposes |
| */ |
| public synchronized void dumpPoolStatus() { |
| _logger.log(Level.INFO, "Name of pool :" + poolInfo); |
| _logger.log(Level.INFO, "Free connections :" + ds.getFreeListSize()); |
| _logger.log(Level.INFO, "Total connections :" + ds.getResourcesSize()); |
| _logger.log(Level.INFO, "Pool's matching is :" + matchConnections); |
| } |
| |
| |
| private void logFine(String msg) { |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.fine(msg); |
| } |
| } |
| |
| //Self management methods |
| public int getMaxPoolSize() { |
| return maxPoolSize; |
| } |
| |
| public int getResizeQuantity() { |
| return resizeQuantity; |
| } |
| |
| public long getIdleTimeout() { |
| return idletime; |
| } |
| |
| public int getWaitQueueLength() { |
| return waitQueue.getQueueLength(); |
| } |
| |
| public int getSteadyPoolSize() { |
| return steadyPoolSize; |
| } |
| |
| |
| public void setMaxPoolSize(int size) { |
| if (size < ds.getResourcesSize()) { |
| synchronized (this) { |
| int toKill = ds.getResourcesSize() - size; |
| if (toKill > 0) { |
| try { |
| killExtraResources(toKill); |
| } catch (Exception re) { |
| //ignore for now |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "setMaxPoolSize:: killExtraResources " + |
| "throws exception: " + re.getMessage()); |
| } |
| } |
| } |
| } |
| } |
| maxPoolSize = size; |
| } |
| |
| public void setSteadyPoolSize(int size) { |
| steadyPoolSize = size; |
| } |
| |
| public void setSelfManaged(boolean selfManaged) { |
| if (_logger.isLoggable(Level.FINE)) { |
| _logger.log(Level.FINE, "Setting selfManaged to : " + selfManaged + " in pool : " + poolInfo); |
| } |
| selfManaged_ = selfManaged; |
| } |
| |
| protected boolean isSelfManaged() { |
| return selfManaged_; |
| } |
| |
| public void potentialConnectionLeakFound() { |
| if (poolLifeCycleListener != null) |
| poolLifeCycleListener.foundPotentialConnectionLeak(); |
| } |
| |
| public void printConnectionLeakTrace(StringBuffer stackTrace) { |
| if (poolLifeCycleListener != null) { |
| String msg = localStrings.getStringWithDefault("monitoring.statistics", "Monitoring Statistics :"); |
| stackTrace.append("\n"); |
| stackTrace.append(msg); |
| stackTrace.append("\n"); |
| //TODO : change toString() to a more specific method name |
| poolLifeCycleListener.toString(stackTrace); |
| } |
| } |
| |
| 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. |
| String msg = localStrings.getString("reclaim.leaked.connection", poolInfo); |
| _logger.log(Level.INFO, msg); |
| ds.removeResource(handle); |
| handle.setDestroyByLeakTimeOut(true); |
| notifyWaitingThreads(); |
| |
| } |
| |
| /** |
| * Get Connection Pool status by computing the free/used values of the |
| * connections in the pool. Computations are based on whether the pool |
| * is initialized or not when this method is invoked. |
| |
| * @return PoolStatus object |
| */ |
| public PoolStatus getPoolStatus() { |
| PoolStatus poolStatus = new PoolStatus(this.poolInfo); |
| int numFree = (this.poolInitialized) ? ds.getFreeListSize() : 0; |
| int numUsed = (this.poolInitialized) ? ds.getResourcesSize()-ds.getFreeListSize() : 0; |
| poolStatus.setNumConnFree(numFree); |
| poolStatus.setNumConnUsed(numUsed); |
| return poolStatus; |
| } |
| } |