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

org.apache.commons.pool2.impl.GenericObjectPool Maven / Gradle / Ivy

There is a newer version: 2.12.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.pool2.impl;

import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.PoolUtils;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.PooledObjectState;
import org.apache.commons.pool2.SwallowedExceptionListener;
import org.apache.commons.pool2.TrackedUse;
import org.apache.commons.pool2.UsageTracking;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A configurable {@link ObjectPool} implementation.
 * 

* When coupled with the appropriate {@link PooledObjectFactory}, * GenericObjectPool provides robust pooling functionality for * arbitrary objects.

*

* Optionally, one may configure the pool to examine and possibly evict objects * as they sit idle in the pool and to ensure that a minimum number of idle * objects are available. This is performed by an "idle object eviction" thread, * which runs asynchronously. Caution should be used when configuring this * optional feature. Eviction runs contend with client threads for access to * objects in the pool, so if they run too frequently performance issues may * result.

*

* The pool can also be configured to detect and remove "abandoned" objects, * i.e. objects that have been checked out of the pool but neither used nor * returned before the configured * {@link AbandonedConfig#getRemoveAbandonedTimeout() removeAbandonedTimeout}. * Abandoned object removal can be configured to happen when * borrowObject is invoked and the pool is close to starvation, or * it can be executed by the idle object evictor, or both. If pooled objects * implement the {@link TrackedUse} interface, their last use will be queried * using the getLastUsed method on that interface; otherwise * abandonment is determined by how long an object has been checked out from * the pool.

*

* Implementation note: To prevent possible deadlocks, care has been taken to * ensure that no call to a factory method will occur within a synchronization * block. See POOL-125 and DBCP-44 for more information.

*

* This class is intended to be thread-safe.

* * @see GenericKeyedObjectPool * * @param Type of element pooled in this pool. * * @since 2.0 */ public class GenericObjectPool extends BaseGenericObjectPool implements ObjectPool, GenericObjectPoolMXBean, UsageTracking { /** * Creates a new GenericObjectPool using defaults from * {@link GenericObjectPoolConfig}. * * @param factory The object factory to be used to create object instances * used by this pool */ public GenericObjectPool(final PooledObjectFactory factory) { this(factory, new GenericObjectPoolConfig()); } /** * Creates a new GenericObjectPool using a specific * configuration. * * @param factory The object factory to be used to create object instances * used by this pool * @param config The configuration to use for this pool instance. The * configuration is used by value. Subsequent changes to * the configuration object will not be reflected in the * pool. */ public GenericObjectPool(final PooledObjectFactory factory, final GenericObjectPoolConfig config) { super(config, ONAME_BASE, config.getJmxNamePrefix()); if (factory == null) { jmxUnregister(); // tidy up throw new IllegalArgumentException("factory may not be null"); } this.factory = factory; idleObjects = new LinkedBlockingDeque<>(config.getFairness()); setConfig(config); } /** * Creates a new GenericObjectPool that tracks and destroys * objects that are checked out, but never returned to the pool. * * @param factory The object factory to be used to create object instances * used by this pool * @param config The base pool configuration to use for this pool instance. * The configuration is used by value. Subsequent changes to * the configuration object will not be reflected in the * pool. * @param abandonedConfig Configuration for abandoned object identification * and removal. The configuration is used by value. */ public GenericObjectPool(final PooledObjectFactory factory, final GenericObjectPoolConfig config, final AbandonedConfig abandonedConfig) { this(factory, config); setAbandonedConfig(abandonedConfig); } /** * Returns the cap on the number of "idle" instances in the pool. If maxIdle * is set too low on heavily loaded systems it is possible you will see * objects being destroyed and almost immediately new objects being created. * This is a result of the active threads momentarily returning objects * faster than they are requesting them, causing the number of idle * objects to rise above maxIdle. The best value for maxIdle for heavily * loaded system will vary but the default is a good starting point. * * @return the maximum number of "idle" instances that can be held in the * pool or a negative value if there is no limit * * @see #setMaxIdle */ @Override public int getMaxIdle() { return maxIdle; } /** * Returns the cap on the number of "idle" instances in the pool. If maxIdle * is set too low on heavily loaded systems it is possible you will see * objects being destroyed and almost immediately new objects being created. * This is a result of the active threads momentarily returning objects * faster than they are requesting them, causing the number of idle * objects to rise above maxIdle. The best value for maxIdle for heavily * loaded system will vary but the default is a good starting point. * * @param maxIdle * The cap on the number of "idle" instances in the pool. Use a * negative value to indicate an unlimited number of idle * instances * * @see #getMaxIdle */ public void setMaxIdle(final int maxIdle) { this.maxIdle = maxIdle; } /** * Sets the target for the minimum number of idle objects to maintain in * the pool. This setting only has an effect if it is positive and * {@link #getTimeBetweenEvictionRunsMillis()} is greater than zero. If this * is the case, an attempt is made to ensure that the pool has the required * minimum number of instances during idle object eviction runs. *

* If the configured value of minIdle is greater than the configured value * for maxIdle then the value of maxIdle will be used instead. * * @param minIdle * The minimum number of objects. * * @see #getMinIdle() * @see #getMaxIdle() * @see #getTimeBetweenEvictionRunsMillis() */ public void setMinIdle(final int minIdle) { this.minIdle = minIdle; } /** * Returns the target for the minimum number of idle objects to maintain in * the pool. This setting only has an effect if it is positive and * {@link #getTimeBetweenEvictionRunsMillis()} is greater than zero. If this * is the case, an attempt is made to ensure that the pool has the required * minimum number of instances during idle object eviction runs. *

* If the configured value of minIdle is greater than the configured value * for maxIdle then the value of maxIdle will be used instead. * * @return The minimum number of objects. * * @see #setMinIdle(int) * @see #setMaxIdle(int) * @see #setTimeBetweenEvictionRunsMillis(long) */ @Override public int getMinIdle() { final int maxIdleSave = getMaxIdle(); if (this.minIdle > maxIdleSave) { return maxIdleSave; } return minIdle; } /** * Gets whether or not abandoned object removal is configured for this pool. * * @return true if this pool is configured to detect and remove * abandoned objects */ @Override public boolean isAbandonedConfig() { return abandonedConfig != null; } /** * Gets whether this pool identifies and logs any abandoned objects. * * @return {@code true} if abandoned object removal is configured for this * pool and removal events are to be logged otherwise {@code false} * * @see AbandonedConfig#getLogAbandoned() */ @Override public boolean getLogAbandoned() { final AbandonedConfig ac = this.abandonedConfig; return ac != null && ac.getLogAbandoned(); } /** * Gets whether a check is made for abandoned objects when an object is borrowed * from this pool. * * @return {@code true} if abandoned object removal is configured to be * activated by borrowObject otherwise {@code false} * * @see AbandonedConfig#getRemoveAbandonedOnBorrow() */ @Override public boolean getRemoveAbandonedOnBorrow() { final AbandonedConfig ac = this.abandonedConfig; return ac != null && ac.getRemoveAbandonedOnBorrow(); } /** * Gets whether a check is made for abandoned objects when the evictor runs. * * @return {@code true} if abandoned object removal is configured to be * activated when the evictor runs otherwise {@code false} * * @see AbandonedConfig#getRemoveAbandonedOnMaintenance() */ @Override public boolean getRemoveAbandonedOnMaintenance() { final AbandonedConfig ac = this.abandonedConfig; return ac != null && ac.getRemoveAbandonedOnMaintenance(); } /** * Obtains the timeout before which an object will be considered to be * abandoned by this pool. * * @return The abandoned object timeout in seconds if abandoned object * removal is configured for this pool; Integer.MAX_VALUE otherwise. * * @see AbandonedConfig#getRemoveAbandonedTimeout() */ @Override public int getRemoveAbandonedTimeout() { final AbandonedConfig ac = this.abandonedConfig; return ac != null ? ac.getRemoveAbandonedTimeout() : Integer.MAX_VALUE; } /** * Sets the base pool configuration. * * @param conf the new configuration to use. This is used by value. * * @see GenericObjectPoolConfig */ public void setConfig(final GenericObjectPoolConfig conf) { super.setConfig(conf); setMaxIdle(conf.getMaxIdle()); setMinIdle(conf.getMinIdle()); setMaxTotal(conf.getMaxTotal()); } /** * Sets the abandoned object removal configuration. * * @param abandonedConfig the new configuration to use. This is used by value. * * @see AbandonedConfig */ public void setAbandonedConfig(final AbandonedConfig abandonedConfig) { if (abandonedConfig == null) { this.abandonedConfig = null; } else { this.abandonedConfig = new AbandonedConfig(); this.abandonedConfig.setLogAbandoned(abandonedConfig.getLogAbandoned()); this.abandonedConfig.setLogWriter(abandonedConfig.getLogWriter()); this.abandonedConfig.setRemoveAbandonedOnBorrow(abandonedConfig.getRemoveAbandonedOnBorrow()); this.abandonedConfig.setRemoveAbandonedOnMaintenance(abandonedConfig.getRemoveAbandonedOnMaintenance()); this.abandonedConfig.setRemoveAbandonedTimeout(abandonedConfig.getRemoveAbandonedTimeout()); this.abandonedConfig.setUseUsageTracking(abandonedConfig.getUseUsageTracking()); this.abandonedConfig.setRequireFullStackTrace(abandonedConfig.getRequireFullStackTrace()); } } /** * Obtains a reference to the factory used to create, destroy and validate * the objects used by this pool. * * @return the factory */ public PooledObjectFactory getFactory() { return factory; } /** * Equivalent to {@link #borrowObject(long) * borrowObject}({@link #getMaxWaitMillis()}). *

* {@inheritDoc} */ @Override public T borrowObject() throws Exception { return borrowObject(getMaxWaitMillis()); } /** * Borrows an object from the pool using the specific waiting time which only * applies if {@link #getBlockWhenExhausted()} is true. *

* If there is one or more idle instance available in the pool, then an * idle instance will be selected based on the value of {@link #getLifo()}, * activated and returned. If activation fails, or {@link #getTestOnBorrow() * testOnBorrow} is set to true and validation fails, the * instance is destroyed and the next available instance is examined. This * continues until either a valid instance is returned or there are no more * idle instances available. *

* If there are no idle instances available in the pool, behavior depends on * the {@link #getMaxTotal() maxTotal}, (if applicable) * {@link #getBlockWhenExhausted()} and the value passed in to the * borrowMaxWaitMillis parameter. If the number of instances * checked out from the pool is less than maxTotal, a new * instance is created, activated and (if applicable) validated and returned * to the caller. If validation fails, a NoSuchElementException * is thrown. *

* If the pool is exhausted (no available idle instances and no capacity to * create new ones), this method will either block (if * {@link #getBlockWhenExhausted()} is true) or throw a * NoSuchElementException (if * {@link #getBlockWhenExhausted()} is false). The length of time that this * method will block when {@link #getBlockWhenExhausted()} is true is * determined by the value passed in to the borrowMaxWaitMillis * parameter. *

* When the pool is exhausted, multiple calling threads may be * simultaneously blocked waiting for instances to become available. A * "fairness" algorithm has been implemented to ensure that threads receive * available instances in request arrival order. * * @param borrowMaxWaitMillis The time to wait in milliseconds for an object * to become available * * @return object instance from the pool * * @throws NoSuchElementException if an instance cannot be returned * * @throws Exception if an object instance cannot be returned due to an * error */ public T borrowObject(final long borrowMaxWaitMillis) throws Exception { assertOpen(); final AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) && (getNumActive() > getMaxTotal() - 3) ) { removeAbandoned(ac); } PooledObject p = null; // Get local copy of current config so it is consistent for entire // method execution final boolean blockWhenExhausted = getBlockWhenExhausted(); boolean create; final long waitTime = System.currentTimeMillis(); while (p == null) { create = false; p = idleObjects.pollFirst(); if (p == null) { p = create(); if (p != null) { create = true; } } if (blockWhenExhausted) { if (p == null) { if (borrowMaxWaitMillis < 0) { p = idleObjects.takeFirst(); } else { p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS); } } if (p == null) { throw new NoSuchElementException( "Timeout waiting for idle object"); } } else { if (p == null) { throw new NoSuchElementException("Pool exhausted"); } } if (!p.allocate()) { p = null; } if (p != null) { try { factory.activateObject(p); } catch (final Exception e) { try { destroy(p); } catch (final Exception e1) { // Ignore - activation failure is more important } p = null; if (create) { final NoSuchElementException nsee = new NoSuchElementException( "Unable to activate object"); nsee.initCause(e); throw nsee; } } if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) { boolean validate = false; Throwable validationThrowable = null; try { validate = factory.validateObject(p); } catch (final Throwable t) { PoolUtils.checkRethrow(t); validationThrowable = t; } if (!validate) { try { destroy(p); destroyedByBorrowValidationCount.incrementAndGet(); } catch (final Exception e) { // Ignore - validation failure is more important } p = null; if (create) { final NoSuchElementException nsee = new NoSuchElementException( "Unable to validate object"); nsee.initCause(validationThrowable); throw nsee; } } } } } updateStatsBorrow(p, System.currentTimeMillis() - waitTime); return p.getObject(); } /** * {@inheritDoc} *

* If {@link #getMaxIdle() maxIdle} is set to a positive value and the * number of idle instances has reached this value, the returning instance * is destroyed. *

* If {@link #getTestOnReturn() testOnReturn} == true, the returning * instance is validated before being returned to the idle instance pool. In * this case, if validation fails, the instance is destroyed. *

* Exceptions encountered destroying objects for any reason are swallowed * but notified via a {@link SwallowedExceptionListener}. */ @Override public void returnObject(final T obj) { final PooledObject p = allObjects.get(new IdentityWrapper<>(obj)); if (p == null) { if (!isAbandonedConfig()) { throw new IllegalStateException( "Returned object not currently part of this pool"); } return; // Object was abandoned and removed } markReturningState(p); final long activeTime = p.getActiveTimeMillis(); if (getTestOnReturn() && !factory.validateObject(p)) { try { destroy(p); } catch (final Exception e) { swallowException(e); } try { ensureIdle(1, false); } catch (final Exception e) { swallowException(e); } updateStatsReturn(activeTime); return; } try { factory.passivateObject(p); } catch (final Exception e1) { swallowException(e1); try { destroy(p); } catch (final Exception e) { swallowException(e); } try { ensureIdle(1, false); } catch (final Exception e) { swallowException(e); } updateStatsReturn(activeTime); return; } if (!p.deallocate()) { throw new IllegalStateException( "Object has already been returned to this pool or is invalid"); } final int maxIdleSave = getMaxIdle(); if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) { try { destroy(p); } catch (final Exception e) { swallowException(e); } } else { if (getLifo()) { idleObjects.addFirst(p); } else { idleObjects.addLast(p); } if (isClosed()) { // Pool closed while object was being added to idle objects. // Make sure the returned object is destroyed rather than left // in the idle object pool (which would effectively be a leak) clear(); } } updateStatsReturn(activeTime); } /** * {@inheritDoc} *

* Activation of this method decrements the active count and attempts to * destroy the instance. * * @throws Exception if an exception occurs destroying the * object * @throws IllegalStateException if obj does not belong to this pool */ @Override public void invalidateObject(final T obj) throws Exception { final PooledObject p = allObjects.get(new IdentityWrapper<>(obj)); if (p == null) { if (isAbandonedConfig()) { return; } throw new IllegalStateException( "Invalidated object not currently part of this pool"); } synchronized (p) { if (p.getState() != PooledObjectState.INVALID) { destroy(p); } } ensureIdle(1, false); } /** * Clears any objects sitting idle in the pool by removing them from the * idle instance pool and then invoking the configured * {@link PooledObjectFactory#destroyObject(PooledObject)} method on each * idle instance. *

* Implementation notes: *

    *
  • This method does not destroy or effect in any way instances that are * checked out of the pool when it is invoked.
  • *
  • Invoking this method does not prevent objects being returned to the * idle instance pool, even during its execution. Additional instances may * be returned while removed items are being destroyed.
  • *
  • Exceptions encountered destroying idle instances are swallowed * but notified via a {@link SwallowedExceptionListener}.
  • *
*/ @Override public void clear() { PooledObject p = idleObjects.poll(); while (p != null) { try { destroy(p); } catch (final Exception e) { swallowException(e); } p = idleObjects.poll(); } } @Override public int getNumActive() { return allObjects.size() - idleObjects.size(); } @Override public int getNumIdle() { return idleObjects.size(); } /** * Closes the pool. Once the pool is closed, {@link #borrowObject()} will * fail with IllegalStateException, but {@link #returnObject(Object)} and * {@link #invalidateObject(Object)} will continue to work, with returned * objects destroyed on return. *

* Destroys idle instances in the pool by invoking {@link #clear()}. */ @Override public void close() { if (isClosed()) { return; } synchronized (closeLock) { if (isClosed()) { return; } // Stop the evictor before the pool is closed since evict() calls // assertOpen() stopEvitor(); closed = true; // This clear removes any idle objects clear(); jmxUnregister(); // Release any threads that were waiting for an object idleObjects.interuptTakeWaiters(); } } /** * {@inheritDoc} *

* Successive activations of this method examine objects in sequence, * cycling through objects in oldest-to-youngest order. */ @Override public void evict() throws Exception { assertOpen(); if (idleObjects.size() > 0) { PooledObject underTest = null; final EvictionPolicy evictionPolicy = getEvictionPolicy(); synchronized (evictionLock) { final EvictionConfig evictionConfig = new EvictionConfig( getMinEvictableIdleTimeMillis(), getSoftMinEvictableIdleTimeMillis(), getMinIdle()); final boolean testWhileIdle = getTestWhileIdle(); for (int i = 0, m = getNumTests(); i < m; i++) { if (evictionIterator == null || !evictionIterator.hasNext()) { evictionIterator = new EvictionIterator(idleObjects); } if (!evictionIterator.hasNext()) { // Pool exhausted, nothing to do here return; } try { underTest = evictionIterator.next(); } catch (final NoSuchElementException nsee) { // Object was borrowed in another thread // Don't count this as an eviction test so reduce i; i--; evictionIterator = null; continue; } if (!underTest.startEvictionTest()) { // Object was borrowed in another thread // Don't count this as an eviction test so reduce i; i--; continue; } // User provided eviction policy could throw all sorts of // crazy exceptions. Protect against such an exception // killing the eviction thread. boolean evict; try { evict = evictionPolicy.evict(evictionConfig, underTest, idleObjects.size()); } catch (final Throwable t) { // Slightly convoluted as SwallowedExceptionListener // uses Exception rather than Throwable PoolUtils.checkRethrow(t); swallowException(new Exception(t)); // Don't evict on error conditions evict = false; } if (evict) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } else { if (testWhileIdle) { boolean active = false; try { factory.activateObject(underTest); active = true; } catch (final Exception e) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } if (active) { if (!factory.validateObject(underTest)) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } else { try { factory.passivateObject(underTest); } catch (final Exception e) { destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } } } } if (!underTest.endEvictionTest(idleObjects)) { // TODO - May need to add code here once additional // states are used } } } } } final AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnMaintenance()) { removeAbandoned(ac); } } /** * Tries to ensure that {@link #getMinIdle()} idle instances are available * in the pool. * * @throws Exception If the associated factory throws an exception * @since 2.4 */ public void preparePool() throws Exception { if (getMinIdle() < 1) { return; } ensureMinIdle(); } /** * Attempts to create a new wrapped pooled object. *

* If there are {@link #getMaxTotal()} objects already in circulation * or in process of being created, this method returns null. * * @return The new wrapped pooled object * * @throws Exception if the object factory's {@code makeObject} fails */ private PooledObject create() throws Exception { int localMaxTotal = getMaxTotal(); // This simplifies the code later in this method if (localMaxTotal < 0) { localMaxTotal = Integer.MAX_VALUE; } final long localStartTimeMillis = System.currentTimeMillis(); final long localMaxWaitTimeMillis = Math.max(getMaxWaitMillis(), 0); // Flag that indicates if create should: // - TRUE: call the factory to create an object // - FALSE: return null // - null: loop and re-test the condition that determines whether to // call the factory Boolean create = null; while (create == null) { synchronized (makeObjectCountLock) { final long newCreateCount = createCount.incrementAndGet(); if (newCreateCount > localMaxTotal) { // The pool is currently at capacity or in the process of // making enough new objects to take it to capacity. createCount.decrementAndGet(); if (makeObjectCount == 0) { // There are no makeObject() calls in progress so the // pool is at capacity. Do not attempt to create a new // object. Return and wait for an object to be returned create = Boolean.FALSE; } else { // There are makeObject() calls in progress that might // bring the pool to capacity. Those calls might also // fail so wait until they complete and then re-test if // the pool is at capacity or not. makeObjectCountLock.wait(localMaxWaitTimeMillis); } } else { // The pool is not at capacity. Create a new object. makeObjectCount++; create = Boolean.TRUE; } } // Do not block more if maxWaitTimeMillis is set. if (create == null && (localMaxWaitTimeMillis > 0 && System.currentTimeMillis() - localStartTimeMillis >= localMaxWaitTimeMillis)) { create = Boolean.FALSE; } } if (!create.booleanValue()) { return null; } final PooledObject p; try { p = factory.makeObject(); } catch (final Throwable e) { createCount.decrementAndGet(); throw e; } finally { synchronized (makeObjectCountLock) { makeObjectCount--; makeObjectCountLock.notifyAll(); } } final AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getLogAbandoned()) { p.setLogAbandoned(true); // TODO: in 3.0, this can use the method defined on PooledObject if (p instanceof DefaultPooledObject) { ((DefaultPooledObject) p).setRequireFullStackTrace(ac.getRequireFullStackTrace()); } } createdCount.incrementAndGet(); allObjects.put(new IdentityWrapper<>(p.getObject()), p); return p; } /** * Destroys a wrapped pooled object. * * @param toDestroy The wrapped pooled object to destroy * * @throws Exception If the factory fails to destroy the pooled object * cleanly */ private void destroy(final PooledObject toDestroy) throws Exception { toDestroy.invalidate(); idleObjects.remove(toDestroy); allObjects.remove(new IdentityWrapper<>(toDestroy.getObject())); try { factory.destroyObject(toDestroy); } finally { destroyedCount.incrementAndGet(); createCount.decrementAndGet(); } if (idleObjects.isEmpty() && idleObjects.hasTakeWaiters()) { // POOL-356. // In case there are already threads waiting on something in the pool // (e.g. idleObjects.takeFirst(); then we need to provide them a fresh instance. // Otherwise they will be stuck forever (or until timeout) final PooledObject freshPooled = create(); idleObjects.put(freshPooled); } } @Override void ensureMinIdle() throws Exception { ensureIdle(getMinIdle(), true); } /** * Tries to ensure that {@code idleCount} idle instances exist in the pool. *

* Creates and adds idle instances until either {@link #getNumIdle()} reaches {@code idleCount} * or the total number of objects (idle, checked out, or being created) reaches * {@link #getMaxTotal()}. If {@code always} is false, no instances are created unless * there are threads waiting to check out instances from the pool. * * @param idleCount the number of idle instances desired * @param always true means create instances even if the pool has no threads waiting * @throws Exception if the factory's makeObject throws */ private void ensureIdle(final int idleCount, final boolean always) throws Exception { if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) { return; } while (idleObjects.size() < idleCount) { final PooledObject p = create(); if (p == null) { // Can't create objects, no reason to think another call to // create will work. Give up. break; } if (getLifo()) { idleObjects.addFirst(p); } else { idleObjects.addLast(p); } } if (isClosed()) { // Pool closed while object was being added to idle objects. // Make sure the returned object is destroyed rather than left // in the idle object pool (which would effectively be a leak) clear(); } } /** * Creates an object, and place it into the pool. addObject() is useful for * "pre-loading" a pool with idle objects. *

* If there is no capacity available to add to the pool, this is a no-op * (no exception, no impact to the pool).

*/ @Override public void addObject() throws Exception { assertOpen(); if (factory == null) { throw new IllegalStateException( "Cannot add objects without a factory."); } final PooledObject p = create(); addIdleObject(p); } /** * Adds the provided wrapped pooled object to the set of idle objects for * this pool. The object must already be part of the pool. If {@code p} * is null, this is a no-op (no exception, but no impact on the pool). * * @param p The object to make idle * * @throws Exception If the factory fails to passivate the object */ private void addIdleObject(final PooledObject p) throws Exception { if (p != null) { factory.passivateObject(p); if (getLifo()) { idleObjects.addFirst(p); } else { idleObjects.addLast(p); } } } /** * Calculates the number of objects to test in a run of the idle object * evictor. * * @return The number of objects to test for validity */ private int getNumTests() { final int numTestsPerEvictionRun = getNumTestsPerEvictionRun(); if (numTestsPerEvictionRun >= 0) { return Math.min(numTestsPerEvictionRun, idleObjects.size()); } return (int) (Math.ceil(idleObjects.size() / Math.abs((double) numTestsPerEvictionRun))); } /** * Recovers abandoned objects which have been checked out but * not used since longer than the removeAbandonedTimeout. * * @param ac The configuration to use to identify abandoned objects */ private void removeAbandoned(final AbandonedConfig ac) { // Generate a list of abandoned objects to remove final long now = System.currentTimeMillis(); final long timeout = now - (ac.getRemoveAbandonedTimeout() * 1000L); final ArrayList> remove = new ArrayList<>(); final Iterator> it = allObjects.values().iterator(); while (it.hasNext()) { final PooledObject pooledObject = it.next(); synchronized (pooledObject) { if (pooledObject.getState() == PooledObjectState.ALLOCATED && pooledObject.getLastUsedTime() <= timeout) { pooledObject.markAbandoned(); remove.add(pooledObject); } } } // Now remove the abandoned objects final Iterator> itr = remove.iterator(); while (itr.hasNext()) { final PooledObject pooledObject = itr.next(); if (ac.getLogAbandoned()) { pooledObject.printStackTrace(ac.getLogWriter()); } try { invalidateObject(pooledObject.getObject()); } catch (final Exception e) { e.printStackTrace(); } } } //--- Usage tracking support ----------------------------------------------- @Override public void use(final T pooledObject) { final AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getUseUsageTracking()) { final PooledObject wrapper = allObjects.get(new IdentityWrapper<>(pooledObject)); wrapper.use(); } } //--- JMX support ---------------------------------------------------------- private volatile String factoryType = null; /** * Returns an estimate of the number of threads currently blocked waiting for * an object from the pool. This is intended for monitoring only, not for * synchronization control. * * @return The estimate of the number of threads currently blocked waiting * for an object from the pool */ @Override public int getNumWaiters() { if (getBlockWhenExhausted()) { return idleObjects.getTakeQueueLength(); } return 0; } /** * Returns the type - including the specific type rather than the generic - * of the factory. * * @return A string representation of the factory type */ @Override public String getFactoryType() { // Not thread safe. Accept that there may be multiple evaluations. if (factoryType == null) { final StringBuilder result = new StringBuilder(); result.append(factory.getClass().getName()); result.append('<'); final Class pooledObjectType = PoolImplUtils.getFactoryType(factory.getClass()); result.append(pooledObjectType.getName()); result.append('>'); factoryType = result.toString(); } return factoryType; } /** * Provides information on all the objects in the pool, both idle (waiting * to be borrowed) and active (currently borrowed). *

* Note: This is named listAllObjects so it is presented as an operation via * JMX. That means it won't be invoked unless the explicitly requested * whereas all attributes will be automatically requested when viewing the * attributes for an object in a tool like JConsole. * * @return Information grouped on all the objects in the pool */ @Override public Set listAllObjects() { final Set result = new HashSet<>(allObjects.size()); for (final PooledObject p : allObjects.values()) { result.add(new DefaultPooledObjectInfo(p)); } return result; } // --- configuration attributes -------------------------------------------- private volatile int maxIdle = GenericObjectPoolConfig.DEFAULT_MAX_IDLE; private volatile int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE; private final PooledObjectFactory factory; // --- internal attributes ------------------------------------------------- /* * All of the objects currently associated with this pool in any state. It * excludes objects that have been destroyed. The size of * {@link #allObjects} will always be less than or equal to {@link * #_maxActive}. Map keys are pooled objects, values are the PooledObject * wrappers used internally by the pool. */ private final Map, PooledObject> allObjects = new ConcurrentHashMap<>(); /* * The combined count of the currently created objects and those in the * process of being created. Under load, it may exceed {@link #_maxActive} * if multiple threads try and create a new object at the same time but * {@link #create()} will ensure that there are never more than * {@link #_maxActive} objects created at any one time. */ private final AtomicLong createCount = new AtomicLong(0); private long makeObjectCount = 0; private final Object makeObjectCountLock = new Object(); private final LinkedBlockingDeque> idleObjects; // JMX specific attributes private static final String ONAME_BASE = "org.apache.commons.pool2:type=GenericObjectPool,name="; // Additional configuration properties for abandoned object tracking private volatile AbandonedConfig abandonedConfig = null; @Override protected void toStringAppendFields(final StringBuilder builder) { super.toStringAppendFields(builder); builder.append(", factoryType="); builder.append(factoryType); builder.append(", maxIdle="); builder.append(maxIdle); builder.append(", minIdle="); builder.append(minIdle); builder.append(", factory="); builder.append(factory); builder.append(", allObjects="); builder.append(allObjects); builder.append(", createCount="); builder.append(createCount); builder.append(", idleObjects="); builder.append(idleObjects); builder.append(", abandonedConfig="); builder.append(abandonedConfig); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy