| /* |
| * 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.tomcat.dbcp.pool2.impl; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.lang.management.ManagementFactory; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Arrays; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.TimerTask; |
| import java.util.concurrent.ScheduledFuture; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicLong; |
| |
| import javax.management.InstanceAlreadyExistsException; |
| import javax.management.InstanceNotFoundException; |
| import javax.management.MBeanRegistrationException; |
| import javax.management.MBeanServer; |
| import javax.management.MalformedObjectNameException; |
| import javax.management.NotCompliantMBeanException; |
| import javax.management.ObjectName; |
| |
| import org.apache.tomcat.dbcp.pool2.BaseObject; |
| import org.apache.tomcat.dbcp.pool2.PooledObject; |
| import org.apache.tomcat.dbcp.pool2.PooledObjectState; |
| import org.apache.tomcat.dbcp.pool2.SwallowedExceptionListener; |
| |
| /** |
| * Base class that provides common functionality for {@link GenericObjectPool} |
| * and {@link GenericKeyedObjectPool}. The primary reason this class exists is |
| * reduce code duplication between the two pool implementations. |
| * |
| * @param <T> Type of element pooled in this pool. |
| * |
| * This class is intended to be thread-safe. |
| * |
| * @since 2.0 |
| */ |
| public abstract class BaseGenericObjectPool<T> extends BaseObject { |
| |
| // Constants |
| /** |
| * The size of the caches used to store historical data for some attributes |
| * so that rolling means may be calculated. |
| */ |
| public static final int MEAN_TIMING_STATS_CACHE_SIZE = 100; |
| |
| private static final String EVICTION_POLICY_TYPE_NAME = EvictionPolicy.class.getName(); |
| |
| // Configuration attributes |
| private volatile int maxTotal = |
| GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; |
| private volatile boolean blockWhenExhausted = |
| BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED; |
| private volatile long maxWaitMillis = |
| BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS; |
| private volatile boolean lifo = BaseObjectPoolConfig.DEFAULT_LIFO; |
| private final boolean fairness; |
| private volatile boolean testOnCreate = |
| BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE; |
| private volatile boolean testOnBorrow = |
| BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW; |
| private volatile boolean testOnReturn = |
| BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN; |
| private volatile boolean testWhileIdle = |
| BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE; |
| private volatile long timeBetweenEvictionRunsMillis = |
| BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; |
| private volatile int numTestsPerEvictionRun = |
| BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; |
| private volatile long minEvictableIdleTimeMillis = |
| BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; |
| private volatile long softMinEvictableIdleTimeMillis = |
| BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS; |
| private volatile EvictionPolicy<T> evictionPolicy; |
| private volatile long evictorShutdownTimeoutMillis = |
| BaseObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS; |
| |
| |
| // Internal (primarily state) attributes |
| final Object closeLock = new Object(); |
| volatile boolean closed = false; |
| final Object evictionLock = new Object(); |
| private Evictor evictor = null; // @GuardedBy("evictionLock") |
| EvictionIterator evictionIterator = null; // @GuardedBy("evictionLock") |
| /* |
| * Class loader for evictor thread to use since, in a JavaEE or similar |
| * environment, the context class loader for the evictor thread may not have |
| * visibility of the correct factory. See POOL-161. Uses a weak reference to |
| * avoid potential memory leaks if the Pool is discarded rather than closed. |
| */ |
| private final WeakReference<ClassLoader> factoryClassLoader; |
| |
| |
| // Monitoring (primarily JMX) attributes |
| private final ObjectName objectName; |
| private final String creationStackTrace; |
| private final AtomicLong borrowedCount = new AtomicLong(0); |
| private final AtomicLong returnedCount = new AtomicLong(0); |
| final AtomicLong createdCount = new AtomicLong(0); |
| final AtomicLong destroyedCount = new AtomicLong(0); |
| final AtomicLong destroyedByEvictorCount = new AtomicLong(0); |
| final AtomicLong destroyedByBorrowValidationCount = new AtomicLong(0); |
| private final StatsStore activeTimes = new StatsStore(MEAN_TIMING_STATS_CACHE_SIZE); |
| private final StatsStore idleTimes = new StatsStore(MEAN_TIMING_STATS_CACHE_SIZE); |
| private final StatsStore waitTimes = new StatsStore(MEAN_TIMING_STATS_CACHE_SIZE); |
| private final AtomicLong maxBorrowWaitTimeMillis = new AtomicLong(0L); |
| private volatile SwallowedExceptionListener swallowedExceptionListener = null; |
| |
| |
| /** |
| * Handles JMX registration (if required) and the initialization required for |
| * monitoring. |
| * |
| * @param config Pool configuration |
| * @param jmxNameBase The default base JMX name for the new pool unless |
| * overridden by the config |
| * @param jmxNamePrefix Prefix to be used for JMX name for the new pool |
| */ |
| public BaseGenericObjectPool(final BaseObjectPoolConfig<T> config, |
| final String jmxNameBase, final String jmxNamePrefix) { |
| if (config.getJmxEnabled()) { |
| this.objectName = jmxRegister(config, jmxNameBase, jmxNamePrefix); |
| } else { |
| this.objectName = null; |
| } |
| |
| // Populate the creation stack trace |
| this.creationStackTrace = getStackTrace(new Exception()); |
| |
| // save the current TCCL (if any) to be used later by the evictor Thread |
| final ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| if (cl == null) { |
| factoryClassLoader = null; |
| } else { |
| factoryClassLoader = new WeakReference<>(cl); |
| } |
| |
| fairness = config.getFairness(); |
| } |
| |
| |
| /** |
| * Returns the maximum number of objects that can be allocated by the pool |
| * (checked out to clients, or idle awaiting checkout) at a given time. When |
| * negative, there is no limit to the number of objects that can be |
| * managed by the pool at one time. |
| * |
| * @return the cap on the total number of object instances managed by the |
| * pool. |
| * |
| * @see #setMaxTotal |
| */ |
| public final int getMaxTotal() { |
| return maxTotal; |
| } |
| |
| /** |
| * Sets the cap on the number of objects that can be allocated by the pool |
| * (checked out to clients, or idle awaiting checkout) at a given time. Use |
| * a negative value for no limit. |
| * |
| * @param maxTotal The cap on the total number of object instances managed |
| * by the pool. Negative values mean that there is no limit |
| * to the number of objects allocated by the pool. |
| * |
| * @see #getMaxTotal |
| */ |
| public final void setMaxTotal(final int maxTotal) { |
| this.maxTotal = maxTotal; |
| } |
| |
| /** |
| * Returns whether to block when the <code>borrowObject()</code> method is |
| * invoked when the pool is exhausted (the maximum number of "active" |
| * objects has been reached). |
| * |
| * @return <code>true</code> if <code>borrowObject()</code> should block |
| * when the pool is exhausted |
| * |
| * @see #setBlockWhenExhausted |
| */ |
| public final boolean getBlockWhenExhausted() { |
| return blockWhenExhausted; |
| } |
| |
| /** |
| * Sets whether to block when the <code>borrowObject()</code> method is |
| * invoked when the pool is exhausted (the maximum number of "active" |
| * objects has been reached). |
| * |
| * @param blockWhenExhausted <code>true</code> if |
| * <code>borrowObject()</code> should block |
| * when the pool is exhausted |
| * |
| * @see #getBlockWhenExhausted |
| */ |
| public final void setBlockWhenExhausted(final boolean blockWhenExhausted) { |
| this.blockWhenExhausted = blockWhenExhausted; |
| } |
| |
| /** |
| * Returns the maximum amount of time (in milliseconds) the |
| * <code>borrowObject()</code> method should block before throwing an |
| * exception when the pool is exhausted and |
| * {@link #getBlockWhenExhausted} is true. When less than 0, the |
| * <code>borrowObject()</code> method may block indefinitely. |
| * |
| * @return the maximum number of milliseconds <code>borrowObject()</code> |
| * will block. |
| * |
| * @see #setMaxWaitMillis |
| * @see #setBlockWhenExhausted |
| */ |
| public final long getMaxWaitMillis() { |
| return maxWaitMillis; |
| } |
| |
| /** |
| * Sets the maximum amount of time (in milliseconds) the |
| * <code>borrowObject()</code> method should block before throwing an |
| * exception when the pool is exhausted and |
| * {@link #getBlockWhenExhausted} is true. When less than 0, the |
| * <code>borrowObject()</code> method may block indefinitely. |
| * |
| * @param maxWaitMillis the maximum number of milliseconds |
| * <code>borrowObject()</code> will block or negative |
| * for indefinitely. |
| * |
| * @see #getMaxWaitMillis |
| * @see #setBlockWhenExhausted |
| */ |
| public final void setMaxWaitMillis(final long maxWaitMillis) { |
| this.maxWaitMillis = maxWaitMillis; |
| } |
| |
| /** |
| * Returns whether the pool has LIFO (last in, first out) behaviour with |
| * respect to idle objects - always returning the most recently used object |
| * from the pool, or as a FIFO (first in, first out) queue, where the pool |
| * always returns the oldest object in the idle object pool. |
| * |
| * @return <code>true</code> if the pool is configured with LIFO behaviour |
| * or <code>false</code> if the pool is configured with FIFO |
| * behaviour |
| * |
| * @see #setLifo |
| */ |
| public final boolean getLifo() { |
| return lifo; |
| } |
| |
| /** |
| * Returns whether or not the pool serves threads waiting to borrow objects fairly. |
| * True means that waiting threads are served as if waiting in a FIFO queue. |
| * |
| * @return <code>true</code> if waiting threads are to be served |
| * by the pool in arrival order |
| */ |
| public final boolean getFairness() { |
| return fairness; |
| } |
| |
| /** |
| * Sets whether the pool has LIFO (last in, first out) behaviour with |
| * respect to idle objects - always returning the most recently used object |
| * from the pool, or as a FIFO (first in, first out) queue, where the pool |
| * always returns the oldest object in the idle object pool. |
| * |
| * @param lifo <code>true</code> if the pool is to be configured with LIFO |
| * behaviour or <code>false</code> if the pool is to be |
| * configured with FIFO behaviour |
| * |
| * @see #getLifo() |
| */ |
| public final void setLifo(final boolean lifo) { |
| this.lifo = lifo; |
| } |
| |
| /** |
| * Returns whether objects created for the pool will be validated before |
| * being returned from the <code>borrowObject()</code> method. Validation is |
| * performed by the <code>validateObject()</code> method of the factory |
| * associated with the pool. If the object fails to validate, then |
| * <code>borrowObject()</code> will fail. |
| * |
| * @return <code>true</code> if newly created objects are validated before |
| * being returned from the <code>borrowObject()</code> method |
| * |
| * @see #setTestOnCreate |
| * |
| * @since 2.2 |
| */ |
| public final boolean getTestOnCreate() { |
| return testOnCreate; |
| } |
| |
| /** |
| * Sets whether objects created for the pool will be validated before |
| * being returned from the <code>borrowObject()</code> method. Validation is |
| * performed by the <code>validateObject()</code> method of the factory |
| * associated with the pool. If the object fails to validate, then |
| * <code>borrowObject()</code> will fail. |
| * |
| * @param testOnCreate <code>true</code> if newly created objects should be |
| * validated before being returned from the |
| * <code>borrowObject()</code> method |
| * |
| * @see #getTestOnCreate |
| * |
| * @since 2.2 |
| */ |
| public final void setTestOnCreate(final boolean testOnCreate) { |
| this.testOnCreate = testOnCreate; |
| } |
| |
| /** |
| * Returns whether objects borrowed from the pool will be validated before |
| * being returned from the <code>borrowObject()</code> method. Validation is |
| * performed by the <code>validateObject()</code> method of the factory |
| * associated with the pool. If the object fails to validate, it will be |
| * removed from the pool and destroyed, and a new attempt will be made to |
| * borrow an object from the pool. |
| * |
| * @return <code>true</code> if objects are validated before being returned |
| * from the <code>borrowObject()</code> method |
| * |
| * @see #setTestOnBorrow |
| */ |
| public final boolean getTestOnBorrow() { |
| return testOnBorrow; |
| } |
| |
| /** |
| * Sets whether objects borrowed from the pool will be validated before |
| * being returned from the <code>borrowObject()</code> method. Validation is |
| * performed by the <code>validateObject()</code> method of the factory |
| * associated with the pool. If the object fails to validate, it will be |
| * removed from the pool and destroyed, and a new attempt will be made to |
| * borrow an object from the pool. |
| * |
| * @param testOnBorrow <code>true</code> if objects should be validated |
| * before being returned from the |
| * <code>borrowObject()</code> method |
| * |
| * @see #getTestOnBorrow |
| */ |
| public final void setTestOnBorrow(final boolean testOnBorrow) { |
| this.testOnBorrow = testOnBorrow; |
| } |
| |
| /** |
| * Returns whether objects borrowed from the pool will be validated when |
| * they are returned to the pool via the <code>returnObject()</code> method. |
| * Validation is performed by the <code>validateObject()</code> method of |
| * the factory associated with the pool. Returning objects that fail validation |
| * are destroyed rather then being returned the pool. |
| * |
| * @return <code>true</code> if objects are validated on return to |
| * the pool via the <code>returnObject()</code> method |
| * |
| * @see #setTestOnReturn |
| */ |
| public final boolean getTestOnReturn() { |
| return testOnReturn; |
| } |
| |
| /** |
| * Sets whether objects borrowed from the pool will be validated when |
| * they are returned to the pool via the <code>returnObject()</code> method. |
| * Validation is performed by the <code>validateObject()</code> method of |
| * the factory associated with the pool. Returning objects that fail validation |
| * are destroyed rather then being returned the pool. |
| * |
| * @param testOnReturn <code>true</code> if objects are validated on |
| * return to the pool via the |
| * <code>returnObject()</code> method |
| * |
| * @see #getTestOnReturn |
| */ |
| public final void setTestOnReturn(final boolean testOnReturn) { |
| this.testOnReturn = testOnReturn; |
| } |
| |
| /** |
| * Returns whether objects sitting idle in the pool will be validated by the |
| * idle object evictor (if any - see |
| * {@link #setTimeBetweenEvictionRunsMillis(long)}). Validation is performed |
| * by the <code>validateObject()</code> method of the factory associated |
| * with the pool. If the object fails to validate, it will be removed from |
| * the pool and destroyed. |
| * |
| * @return <code>true</code> if objects will be validated by the evictor |
| * |
| * @see #setTestWhileIdle |
| * @see #setTimeBetweenEvictionRunsMillis |
| */ |
| public final boolean getTestWhileIdle() { |
| return testWhileIdle; |
| } |
| |
| /** |
| * Returns whether objects sitting idle in the pool will be validated by the |
| * idle object evictor (if any - see |
| * {@link #setTimeBetweenEvictionRunsMillis(long)}). Validation is performed |
| * by the <code>validateObject()</code> method of the factory associated |
| * with the pool. If the object fails to validate, it will be removed from |
| * the pool and destroyed. Note that setting this property has no effect |
| * unless the idle object evictor is enabled by setting |
| * <code>timeBetweenEvictionRunsMillis</code> to a positive value. |
| * |
| * @param testWhileIdle |
| * <code>true</code> so objects will be validated by the evictor |
| * |
| * @see #getTestWhileIdle |
| * @see #setTimeBetweenEvictionRunsMillis |
| */ |
| public final void setTestWhileIdle(final boolean testWhileIdle) { |
| this.testWhileIdle = testWhileIdle; |
| } |
| |
| /** |
| * Returns the number of milliseconds to sleep between runs of the idle |
| * object evictor thread. When non-positive, no idle object evictor thread |
| * will be run. |
| * |
| * @return number of milliseconds to sleep between evictor runs |
| * |
| * @see #setTimeBetweenEvictionRunsMillis |
| */ |
| public final long getTimeBetweenEvictionRunsMillis() { |
| return timeBetweenEvictionRunsMillis; |
| } |
| |
| /** |
| * Sets the number of milliseconds to sleep between runs of the idle object evictor thread. |
| * <ul> |
| * <li>When positive, the idle object evictor thread starts.</li> |
| * <li>When non-positive, no idle object evictor thread runs.</li> |
| * </ul> |
| * |
| * @param timeBetweenEvictionRunsMillis |
| * number of milliseconds to sleep between evictor runs |
| * |
| * @see #getTimeBetweenEvictionRunsMillis |
| */ |
| public final void setTimeBetweenEvictionRunsMillis( |
| final long timeBetweenEvictionRunsMillis) { |
| this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; |
| startEvictor(timeBetweenEvictionRunsMillis); |
| } |
| |
| /** |
| * Returns the maximum number of objects to examine during each run (if any) |
| * of the idle object evictor thread. When positive, the number of tests |
| * performed for a run will be the minimum of the configured value and the |
| * number of idle instances in the pool. When negative, the number of tests |
| * performed will be <code>ceil({@link #getNumIdle}/ |
| * abs({@link #getNumTestsPerEvictionRun}))</code> which means that when the |
| * value is <code>-n</code> roughly one nth of the idle objects will be |
| * tested per run. |
| * |
| * @return max number of objects to examine during each evictor run |
| * |
| * @see #setNumTestsPerEvictionRun |
| * @see #setTimeBetweenEvictionRunsMillis |
| */ |
| public final int getNumTestsPerEvictionRun() { |
| return numTestsPerEvictionRun; |
| } |
| |
| /** |
| * Sets the maximum number of objects to examine during each run (if any) |
| * of the idle object evictor thread. When positive, the number of tests |
| * performed for a run will be the minimum of the configured value and the |
| * number of idle instances in the pool. When negative, the number of tests |
| * performed will be <code>ceil({@link #getNumIdle}/ |
| * abs({@link #getNumTestsPerEvictionRun}))</code> which means that when the |
| * value is <code>-n</code> roughly one nth of the idle objects will be |
| * tested per run. |
| * |
| * @param numTestsPerEvictionRun |
| * max number of objects to examine during each evictor run |
| * |
| * @see #getNumTestsPerEvictionRun |
| * @see #setTimeBetweenEvictionRunsMillis |
| */ |
| public final void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { |
| this.numTestsPerEvictionRun = numTestsPerEvictionRun; |
| } |
| |
| /** |
| * Returns the minimum amount of time an object may sit idle in the pool |
| * before it is eligible for eviction by the idle object evictor (if any - |
| * see {@link #setTimeBetweenEvictionRunsMillis(long)}). When non-positive, |
| * no objects will be evicted from the pool due to idle time alone. |
| * |
| * @return minimum amount of time an object may sit idle in the pool before |
| * it is eligible for eviction |
| * |
| * @see #setMinEvictableIdleTimeMillis |
| * @see #setTimeBetweenEvictionRunsMillis |
| */ |
| public final long getMinEvictableIdleTimeMillis() { |
| return minEvictableIdleTimeMillis; |
| } |
| |
| /** |
| * Sets the minimum amount of time an object may sit idle in the pool |
| * before it is eligible for eviction by the idle object evictor (if any - |
| * see {@link #setTimeBetweenEvictionRunsMillis(long)}). When non-positive, |
| * no objects will be evicted from the pool due to idle time alone. |
| * |
| * @param minEvictableIdleTimeMillis |
| * minimum amount of time an object may sit idle in the pool |
| * before it is eligible for eviction |
| * |
| * @see #getMinEvictableIdleTimeMillis |
| * @see #setTimeBetweenEvictionRunsMillis |
| */ |
| public final void setMinEvictableIdleTimeMillis( |
| final long minEvictableIdleTimeMillis) { |
| this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; |
| } |
| |
| /** |
| * Returns the minimum amount of time an object may sit idle in the pool |
| * before it is eligible for eviction by the idle object evictor (if any - |
| * see {@link #setTimeBetweenEvictionRunsMillis(long)}), |
| * with the extra condition that at least <code>minIdle</code> object |
| * instances remain in the pool. This setting is overridden by |
| * {@link #getMinEvictableIdleTimeMillis} (that is, if |
| * {@link #getMinEvictableIdleTimeMillis} is positive, then |
| * {@link #getSoftMinEvictableIdleTimeMillis} is ignored). |
| * |
| * @return minimum amount of time an object may sit idle in the pool before |
| * it is eligible for eviction if minIdle instances are available |
| * |
| * @see #setSoftMinEvictableIdleTimeMillis |
| */ |
| public final long getSoftMinEvictableIdleTimeMillis() { |
| return softMinEvictableIdleTimeMillis; |
| } |
| |
| /** |
| * Sets the minimum amount of time an object may sit idle in the pool |
| * before it is eligible for eviction by the idle object evictor (if any - |
| * see {@link #setTimeBetweenEvictionRunsMillis(long)}), |
| * with the extra condition that at least <code>minIdle</code> object |
| * instances remain in the pool. This setting is overridden by |
| * {@link #getMinEvictableIdleTimeMillis} (that is, if |
| * {@link #getMinEvictableIdleTimeMillis} is positive, then |
| * {@link #getSoftMinEvictableIdleTimeMillis} is ignored). |
| * |
| * @param softMinEvictableIdleTimeMillis |
| * minimum amount of time an object may sit idle in the pool |
| * before it is eligible for eviction if minIdle instances are |
| * available |
| * |
| * @see #getSoftMinEvictableIdleTimeMillis |
| */ |
| public final void setSoftMinEvictableIdleTimeMillis( |
| final long softMinEvictableIdleTimeMillis) { |
| this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis; |
| } |
| |
| /** |
| * Returns the name of the {@link EvictionPolicy} implementation that is |
| * used by this pool. |
| * |
| * @return The fully qualified class name of the {@link EvictionPolicy} |
| * |
| * @see #setEvictionPolicyClassName(String) |
| */ |
| public final String getEvictionPolicyClassName() { |
| return evictionPolicy.getClass().getName(); |
| } |
| |
| /** |
| * Sets the eviction policy for this pool. |
| * |
| * @param evictionPolicy |
| * the eviction policy for this pool. |
| * @since 2.6.0 |
| */ |
| public void setEvictionPolicy(final EvictionPolicy<T> evictionPolicy) { |
| this.evictionPolicy = evictionPolicy; |
| } |
| |
| /** |
| * Sets the name of the {@link EvictionPolicy} implementation that is used by this pool. The Pool will attempt to |
| * load the class using the given class loader. If that fails, use the class loader for the {@link EvictionPolicy} |
| * interface. |
| * |
| * @param evictionPolicyClassName |
| * the fully qualified class name of the new eviction policy |
| * @param classLoader |
| * the class loader to load the given {@code evictionPolicyClassName}. |
| * |
| * @see #getEvictionPolicyClassName() |
| * @since 2.6.0 If loading the class using the given class loader fails, use the class loader for the |
| * {@link EvictionPolicy} interface. |
| */ |
| public final void setEvictionPolicyClassName(final String evictionPolicyClassName, final ClassLoader classLoader) { |
| // Getting epClass here and now best matches the caller's environment |
| final Class<?> epClass = EvictionPolicy.class; |
| final ClassLoader epClassLoader = epClass.getClassLoader(); |
| try { |
| try { |
| setEvictionPolicy(evictionPolicyClassName, classLoader); |
| } catch (final ClassCastException | ClassNotFoundException e) { |
| setEvictionPolicy(evictionPolicyClassName, epClassLoader); |
| } |
| } catch (final ClassCastException e) { |
| throw new IllegalArgumentException("Class " + evictionPolicyClassName + " from class loaders [" |
| + classLoader + ", " + epClassLoader + "] do not implement " + EVICTION_POLICY_TYPE_NAME); |
| } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException |
| | InvocationTargetException | NoSuchMethodException e) { |
| final String exMessage = "Unable to create " + EVICTION_POLICY_TYPE_NAME + " instance of type " |
| + evictionPolicyClassName; |
| throw new IllegalArgumentException(exMessage, e); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void setEvictionPolicy(final String className, final ClassLoader classLoader) |
| throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { |
| final Class<?> clazz = Class.forName(className, true, classLoader); |
| final Object policy = clazz.getConstructor().newInstance(); |
| this.evictionPolicy = (EvictionPolicy<T>) policy; |
| } |
| |
| /** |
| * Sets the name of the {@link EvictionPolicy} implementation that is used by this pool. The Pool will attempt to |
| * load the class using the thread context class loader. If that fails, the use the class loader for the |
| * {@link EvictionPolicy} interface. |
| * |
| * @param evictionPolicyClassName |
| * the fully qualified class name of the new eviction policy |
| * |
| * @see #getEvictionPolicyClassName() |
| * @since 2.6.0 If loading the class using the thread context class loader fails, use the class loader for the |
| * {@link EvictionPolicy} interface. |
| */ |
| public final void setEvictionPolicyClassName(final String evictionPolicyClassName) { |
| setEvictionPolicyClassName(evictionPolicyClassName, Thread.currentThread().getContextClassLoader()); |
| } |
| |
| /** |
| * Gets the timeout that will be used when waiting for the Evictor to |
| * shutdown if this pool is closed and it is the only pool still using the |
| * the value for the Evictor. |
| * |
| * @return The timeout in milliseconds that will be used while waiting for |
| * the Evictor to shut down. |
| */ |
| public final long getEvictorShutdownTimeoutMillis() { |
| return evictorShutdownTimeoutMillis; |
| } |
| |
| /** |
| * Sets the timeout that will be used when waiting for the Evictor to |
| * shutdown if this pool is closed and it is the only pool still using the |
| * the value for the Evictor. |
| * |
| * @param evictorShutdownTimeoutMillis the timeout in milliseconds that |
| * will be used while waiting for the |
| * Evictor to shut down. |
| */ |
| public final void setEvictorShutdownTimeoutMillis( |
| final long evictorShutdownTimeoutMillis) { |
| this.evictorShutdownTimeoutMillis = evictorShutdownTimeoutMillis; |
| } |
| |
| /** |
| * Closes the pool, destroys the remaining idle objects and, if registered |
| * in JMX, deregisters it. |
| */ |
| public abstract void close(); |
| |
| /** |
| * Has this pool instance been closed. |
| * @return <code>true</code> when this pool has been closed. |
| */ |
| public final boolean isClosed() { |
| return closed; |
| } |
| |
| /** |
| * <p>Perform <code>numTests</code> idle object eviction tests, evicting |
| * examined objects that meet the criteria for eviction. If |
| * <code>testWhileIdle</code> is true, examined objects are validated |
| * when visited (and removed if invalid); otherwise only objects that |
| * have been idle for more than <code>minEvicableIdleTimeMillis</code> |
| * are removed.</p> |
| * |
| * @throws Exception when there is a problem evicting idle objects. |
| */ |
| public abstract void evict() throws Exception; |
| |
| /** |
| * Returns the {@link EvictionPolicy} defined for this pool. |
| * |
| * @return the eviction policy |
| * @since 2.4 |
| * @since 2.6.0 Changed access from protected to public. |
| */ |
| public EvictionPolicy<T> getEvictionPolicy() { |
| return evictionPolicy; |
| } |
| |
| /** |
| * Verifies that the pool is open. |
| * @throws IllegalStateException if the pool is closed. |
| */ |
| final void assertOpen() throws IllegalStateException { |
| if (isClosed()) { |
| throw new IllegalStateException("Pool not open"); |
| } |
| } |
| |
| /** |
| * <p>Starts the evictor with the given delay. If there is an evictor |
| * running when this method is called, it is stopped and replaced with a |
| * new evictor with the specified delay.</p> |
| * |
| * <p>This method needs to be final, since it is called from a constructor. |
| * See POOL-195.</p> |
| * |
| * @param delay time in milliseconds before start and between eviction runs |
| */ |
| final void startEvictor(final long delay) { |
| synchronized (evictionLock) { |
| if (null != evictor) { |
| EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS); |
| evictor = null; |
| evictionIterator = null; |
| } |
| if (delay > 0) { |
| evictor = new Evictor(); |
| EvictionTimer.schedule(evictor, delay, delay); |
| } |
| } |
| } |
| |
| /** |
| * Tries to ensure that the configured minimum number of idle instances are |
| * available in the pool. |
| * @throws Exception if an error occurs creating idle instances |
| */ |
| abstract void ensureMinIdle() throws Exception; |
| |
| |
| // Monitoring (primarily JMX) related methods |
| |
| /** |
| * Provides the name under which the pool has been registered with the |
| * platform MBean server or <code>null</code> if the pool has not been |
| * registered. |
| * @return the JMX name |
| */ |
| public final ObjectName getJmxName() { |
| return objectName; |
| } |
| |
| /** |
| * Provides the stack trace for the call that created this pool. JMX |
| * registration may trigger a memory leak so it is important that pools are |
| * deregistered when no longer used by calling the {@link #close()} method. |
| * This method is provided to assist with identifying code that creates but |
| * does not close it thereby creating a memory leak. |
| * @return pool creation stack trace |
| */ |
| public final String getCreationStackTrace() { |
| return creationStackTrace; |
| } |
| |
| /** |
| * The total number of objects successfully borrowed from this pool over the |
| * lifetime of the pool. |
| * @return the borrowed object count |
| */ |
| public final long getBorrowedCount() { |
| return borrowedCount.get(); |
| } |
| |
| /** |
| * The total number of objects returned to this pool over the lifetime of |
| * the pool. This excludes attempts to return the same object multiple |
| * times. |
| * @return the returned object count |
| */ |
| public final long getReturnedCount() { |
| return returnedCount.get(); |
| } |
| |
| /** |
| * The total number of objects created for this pool over the lifetime of |
| * the pool. |
| * @return the created object count |
| */ |
| public final long getCreatedCount() { |
| return createdCount.get(); |
| } |
| |
| /** |
| * The total number of objects destroyed by this pool over the lifetime of |
| * the pool. |
| * @return the destroyed object count |
| */ |
| public final long getDestroyedCount() { |
| return destroyedCount.get(); |
| } |
| |
| /** |
| * The total number of objects destroyed by the evictor associated with this |
| * pool over the lifetime of the pool. |
| * @return the evictor destroyed object count |
| */ |
| public final long getDestroyedByEvictorCount() { |
| return destroyedByEvictorCount.get(); |
| } |
| |
| /** |
| * The total number of objects destroyed by this pool as a result of failing |
| * validation during <code>borrowObject()</code> over the lifetime of the |
| * pool. |
| * @return validation destroyed object count |
| */ |
| public final long getDestroyedByBorrowValidationCount() { |
| return destroyedByBorrowValidationCount.get(); |
| } |
| |
| /** |
| * The mean time objects are active for based on the last {@link |
| * #MEAN_TIMING_STATS_CACHE_SIZE} objects returned to the pool. |
| * @return mean time an object has been checked out from the pool among |
| * recently returned objects |
| */ |
| public final long getMeanActiveTimeMillis() { |
| return activeTimes.getMean(); |
| } |
| |
| /** |
| * The mean time objects are idle for based on the last {@link |
| * #MEAN_TIMING_STATS_CACHE_SIZE} objects borrowed from the pool. |
| * @return mean time an object has been idle in the pool among recently |
| * borrowed objects |
| */ |
| public final long getMeanIdleTimeMillis() { |
| return idleTimes.getMean(); |
| } |
| |
| /** |
| * The mean time threads wait to borrow an object based on the last {@link |
| * #MEAN_TIMING_STATS_CACHE_SIZE} objects borrowed from the pool. |
| * @return mean time in milliseconds that a recently served thread has had |
| * to wait to borrow an object from the pool |
| */ |
| public final long getMeanBorrowWaitTimeMillis() { |
| return waitTimes.getMean(); |
| } |
| |
| /** |
| * The maximum time a thread has waited to borrow objects from the pool. |
| * @return maximum wait time in milliseconds since the pool was created |
| */ |
| public final long getMaxBorrowWaitTimeMillis() { |
| return maxBorrowWaitTimeMillis.get(); |
| } |
| |
| /** |
| * The number of instances currently idle in this pool. |
| * @return count of instances available for checkout from the pool |
| */ |
| public abstract int getNumIdle(); |
| |
| /** |
| * The listener used (if any) to receive notifications of exceptions |
| * unavoidably swallowed by the pool. |
| * |
| * @return The listener or <code>null</code> for no listener |
| */ |
| public final SwallowedExceptionListener getSwallowedExceptionListener() { |
| return swallowedExceptionListener; |
| } |
| |
| /** |
| * The listener used (if any) to receive notifications of exceptions |
| * unavoidably swallowed by the pool. |
| * |
| * @param swallowedExceptionListener The listener or <code>null</code> |
| * for no listener |
| */ |
| public final void setSwallowedExceptionListener( |
| final SwallowedExceptionListener swallowedExceptionListener) { |
| this.swallowedExceptionListener = swallowedExceptionListener; |
| } |
| |
| /** |
| * Swallows an exception and notifies the configured listener for swallowed |
| * exceptions queue. |
| * |
| * @param swallowException exception to be swallowed |
| */ |
| final void swallowException(final Exception swallowException) { |
| final SwallowedExceptionListener listener = getSwallowedExceptionListener(); |
| |
| if (listener == null) { |
| return; |
| } |
| |
| try { |
| listener.onSwallowException(swallowException); |
| } catch (final VirtualMachineError e) { |
| throw e; |
| } catch (final Throwable t) { |
| // Ignore. Enjoy the irony. |
| } |
| } |
| |
| /** |
| * Updates statistics after an object is borrowed from the pool. |
| * @param p object borrowed from the pool |
| * @param waitTime time (in milliseconds) that the borrowing thread had to wait |
| */ |
| final void updateStatsBorrow(final PooledObject<T> p, final long waitTime) { |
| borrowedCount.incrementAndGet(); |
| idleTimes.add(p.getIdleTimeMillis()); |
| waitTimes.add(waitTime); |
| |
| // lock-free optimistic-locking maximum |
| long currentMax; |
| do { |
| currentMax = maxBorrowWaitTimeMillis.get(); |
| if (currentMax >= waitTime) { |
| break; |
| } |
| } while (!maxBorrowWaitTimeMillis.compareAndSet(currentMax, waitTime)); |
| } |
| |
| /** |
| * Updates statistics after an object is returned to the pool. |
| * @param activeTime the amount of time (in milliseconds) that the returning |
| * object was checked out |
| */ |
| final void updateStatsReturn(final long activeTime) { |
| returnedCount.incrementAndGet(); |
| activeTimes.add(activeTime); |
| } |
| |
| /** |
| * Marks the object as returning to the pool. |
| * @param pooledObject instance to return to the keyed pool |
| */ |
| protected void markReturningState(PooledObject<T> pooledObject) { |
| synchronized(pooledObject) { |
| final PooledObjectState state = pooledObject.getState(); |
| if (state != PooledObjectState.ALLOCATED) { |
| throw new IllegalStateException( |
| "Object has already been returned to this pool or is invalid"); |
| } |
| pooledObject.markReturning(); // Keep from being marked abandoned |
| } |
| } |
| |
| /** |
| * Unregisters this pool's MBean. |
| */ |
| final void jmxUnregister() { |
| if (objectName != null) { |
| try { |
| ManagementFactory.getPlatformMBeanServer().unregisterMBean( |
| objectName); |
| } catch (final MBeanRegistrationException | InstanceNotFoundException e) { |
| swallowException(e); |
| } |
| } |
| } |
| |
| /** |
| * Registers the pool with the platform MBean server. |
| * The registered name will be |
| * <code>jmxNameBase + jmxNamePrefix + i</code> where i is the least |
| * integer greater than or equal to 1 such that the name is not already |
| * registered. Swallows MBeanRegistrationException, NotCompliantMBeanException |
| * returning null. |
| * |
| * @param config Pool configuration |
| * @param jmxNameBase default base JMX name for this pool |
| * @param jmxNamePrefix name prefix |
| * @return registered ObjectName, null if registration fails |
| */ |
| private ObjectName jmxRegister(final BaseObjectPoolConfig<T> config, |
| final String jmxNameBase, String jmxNamePrefix) { |
| ObjectName objectName = null; |
| final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); |
| int i = 1; |
| boolean registered = false; |
| String base = config.getJmxNameBase(); |
| if (base == null) { |
| base = jmxNameBase; |
| } |
| while (!registered) { |
| try { |
| ObjectName objName; |
| // Skip the numeric suffix for the first pool in case there is |
| // only one so the names are cleaner. |
| if (i == 1) { |
| objName = new ObjectName(base + jmxNamePrefix); |
| } else { |
| objName = new ObjectName(base + jmxNamePrefix + i); |
| } |
| mbs.registerMBean(this, objName); |
| objectName = objName; |
| registered = true; |
| } catch (final MalformedObjectNameException e) { |
| if (BaseObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX.equals( |
| jmxNamePrefix) && jmxNameBase.equals(base)) { |
| // Shouldn't happen. Skip registration if it does. |
| registered = true; |
| } else { |
| // Must be an invalid name. Use the defaults instead. |
| jmxNamePrefix = |
| BaseObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX; |
| base = jmxNameBase; |
| } |
| } catch (final InstanceAlreadyExistsException e) { |
| // Increment the index and try again |
| i++; |
| } catch (final MBeanRegistrationException | NotCompliantMBeanException e) { |
| // Shouldn't happen. Skip registration if it does. |
| registered = true; |
| } |
| } |
| return objectName; |
| } |
| |
| /** |
| * Gets the stack trace of an exception as a string. |
| * @param e exception to trace |
| * @return exception stack trace as a string |
| */ |
| private String getStackTrace(final Exception e) { |
| // Need the exception in string form to prevent the retention of |
| // references to classes in the stack trace that could trigger a memory |
| // leak in a container environment. |
| final Writer w = new StringWriter(); |
| final PrintWriter pw = new PrintWriter(w); |
| e.printStackTrace(pw); |
| return w.toString(); |
| } |
| |
| // Inner classes |
| |
| /** |
| * The idle object evictor {@link TimerTask}. |
| * |
| * @see GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis |
| */ |
| class Evictor implements Runnable { |
| |
| private ScheduledFuture<?> scheduledFuture; |
| |
| /** |
| * Run pool maintenance. Evict objects qualifying for eviction and then |
| * ensure that the minimum number of idle instances are available. |
| * Since the Timer that invokes Evictors is shared for all Pools but |
| * pools may exist in different class loaders, the Evictor ensures that |
| * any actions taken are under the class loader of the factory |
| * associated with the pool. |
| */ |
| @Override |
| public void run() { |
| final ClassLoader savedClassLoader = |
| Thread.currentThread().getContextClassLoader(); |
| try { |
| if (factoryClassLoader != null) { |
| // Set the class loader for the factory |
| final ClassLoader cl = factoryClassLoader.get(); |
| if (cl == null) { |
| // The pool has been dereferenced and the class loader |
| // GC'd. Cancel this timer so the pool can be GC'd as |
| // well. |
| cancel(); |
| return; |
| } |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| |
| // Evict from the pool |
| try { |
| evict(); |
| } catch(final Exception e) { |
| swallowException(e); |
| } catch(final OutOfMemoryError oome) { |
| // Log problem but give evictor thread a chance to continue |
| // in case error is recoverable |
| oome.printStackTrace(System.err); |
| } |
| // Re-create idle instances. |
| try { |
| ensureMinIdle(); |
| } catch (final Exception e) { |
| swallowException(e); |
| } |
| } finally { |
| // Restore the previous CCL |
| Thread.currentThread().setContextClassLoader(savedClassLoader); |
| } |
| } |
| |
| |
| void setScheduledFuture(final ScheduledFuture<?> scheduledFuture) { |
| this.scheduledFuture = scheduledFuture; |
| } |
| |
| |
| void cancel() { |
| scheduledFuture.cancel(false); |
| } |
| } |
| |
| /** |
| * Maintains a cache of values for a single metric and reports |
| * statistics on the cached values. |
| */ |
| private class StatsStore { |
| |
| private final AtomicLong values[]; |
| private final int size; |
| private int index; |
| |
| /** |
| * Create a StatsStore with the given cache size. |
| * |
| * @param size number of values to maintain in the cache. |
| */ |
| public StatsStore(final int size) { |
| this.size = size; |
| values = new AtomicLong[size]; |
| for (int i = 0; i < size; i++) { |
| values[i] = new AtomicLong(-1); |
| } |
| } |
| |
| /** |
| * Adds a value to the cache. If the cache is full, one of the |
| * existing values is replaced by the new value. |
| * |
| * @param value new value to add to the cache. |
| */ |
| public synchronized void add(final long value) { |
| values[index].set(value); |
| index++; |
| if (index == size) { |
| index = 0; |
| } |
| } |
| |
| /** |
| * Returns the mean of the cached values. |
| * |
| * @return the mean of the cache, truncated to long |
| */ |
| public long getMean() { |
| double result = 0; |
| int counter = 0; |
| for (int i = 0; i < size; i++) { |
| final long value = values[i].get(); |
| if (value != -1) { |
| counter++; |
| result = result * ((counter - 1) / (double) counter) + |
| value/(double) counter; |
| } |
| } |
| return (long) result; |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder builder = new StringBuilder(); |
| builder.append("StatsStore [values="); |
| builder.append(Arrays.toString(values)); |
| builder.append(", size="); |
| builder.append(size); |
| builder.append(", index="); |
| builder.append(index); |
| builder.append("]"); |
| return builder.toString(); |
| } |
| } |
| |
| /** |
| * The idle object eviction iterator. Holds a reference to the idle objects. |
| */ |
| class EvictionIterator implements Iterator<PooledObject<T>> { |
| |
| private final Deque<PooledObject<T>> idleObjects; |
| private final Iterator<PooledObject<T>> idleObjectIterator; |
| |
| /** |
| * Create an EvictionIterator for the provided idle instance deque. |
| * @param idleObjects underlying deque |
| */ |
| EvictionIterator(final Deque<PooledObject<T>> idleObjects) { |
| this.idleObjects = idleObjects; |
| |
| if (getLifo()) { |
| idleObjectIterator = idleObjects.descendingIterator(); |
| } else { |
| idleObjectIterator = idleObjects.iterator(); |
| } |
| } |
| |
| /** |
| * Returns the idle object deque referenced by this iterator. |
| * @return the idle object deque |
| */ |
| public Deque<PooledObject<T>> getIdleObjects() { |
| return idleObjects; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean hasNext() { |
| return idleObjectIterator.hasNext(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public PooledObject<T> next() { |
| return idleObjectIterator.next(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void remove() { |
| idleObjectIterator.remove(); |
| } |
| |
| } |
| |
| /** |
| * Wrapper for objects under management by the pool. |
| * |
| * GenericObjectPool and GenericKeyedObjectPool maintain references to all |
| * objects under management using maps keyed on the objects. This wrapper |
| * class ensures that objects can work as hash keys. |
| * |
| * @param <T> type of objects in the pool |
| */ |
| static class IdentityWrapper<T> { |
| /** Wrapped object */ |
| private final T instance; |
| |
| /** |
| * Create a wrapper for an instance. |
| * |
| * @param instance object to wrap |
| */ |
| public IdentityWrapper(final T instance) { |
| this.instance = instance; |
| } |
| |
| @Override |
| public int hashCode() { |
| return System.identityHashCode(instance); |
| } |
| |
| @Override |
| @SuppressWarnings("rawtypes") |
| public boolean equals(final Object other) { |
| return other instanceof IdentityWrapper && |
| ((IdentityWrapper) other).instance == instance; |
| } |
| |
| /** |
| * @return the wrapped object |
| */ |
| public T getObject() { |
| return instance; |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder builder = new StringBuilder(); |
| builder.append("IdentityWrapper [instance="); |
| builder.append(instance); |
| builder.append("]"); |
| return builder.toString(); |
| } |
| } |
| |
| @Override |
| protected void toStringAppendFields(final StringBuilder builder) { |
| builder.append("maxTotal="); |
| builder.append(maxTotal); |
| builder.append(", blockWhenExhausted="); |
| builder.append(blockWhenExhausted); |
| builder.append(", maxWaitMillis="); |
| builder.append(maxWaitMillis); |
| builder.append(", lifo="); |
| builder.append(lifo); |
| builder.append(", fairness="); |
| builder.append(fairness); |
| builder.append(", testOnCreate="); |
| builder.append(testOnCreate); |
| builder.append(", testOnBorrow="); |
| builder.append(testOnBorrow); |
| builder.append(", testOnReturn="); |
| builder.append(testOnReturn); |
| builder.append(", testWhileIdle="); |
| builder.append(testWhileIdle); |
| builder.append(", timeBetweenEvictionRunsMillis="); |
| builder.append(timeBetweenEvictionRunsMillis); |
| builder.append(", numTestsPerEvictionRun="); |
| builder.append(numTestsPerEvictionRun); |
| builder.append(", minEvictableIdleTimeMillis="); |
| builder.append(minEvictableIdleTimeMillis); |
| builder.append(", softMinEvictableIdleTimeMillis="); |
| builder.append(softMinEvictableIdleTimeMillis); |
| builder.append(", evictionPolicy="); |
| builder.append(evictionPolicy); |
| builder.append(", closeLock="); |
| builder.append(closeLock); |
| builder.append(", closed="); |
| builder.append(closed); |
| builder.append(", evictionLock="); |
| builder.append(evictionLock); |
| builder.append(", evictor="); |
| builder.append(evictor); |
| builder.append(", evictionIterator="); |
| builder.append(evictionIterator); |
| builder.append(", factoryClassLoader="); |
| builder.append(factoryClassLoader); |
| builder.append(", oname="); |
| builder.append(objectName); |
| builder.append(", creationStackTrace="); |
| builder.append(creationStackTrace); |
| builder.append(", borrowedCount="); |
| builder.append(borrowedCount); |
| builder.append(", returnedCount="); |
| builder.append(returnedCount); |
| builder.append(", createdCount="); |
| builder.append(createdCount); |
| builder.append(", destroyedCount="); |
| builder.append(destroyedCount); |
| builder.append(", destroyedByEvictorCount="); |
| builder.append(destroyedByEvictorCount); |
| builder.append(", destroyedByBorrowValidationCount="); |
| builder.append(destroyedByBorrowValidationCount); |
| builder.append(", activeTimes="); |
| builder.append(activeTimes); |
| builder.append(", idleTimes="); |
| builder.append(idleTimes); |
| builder.append(", waitTimes="); |
| builder.append(waitTimes); |
| builder.append(", maxBorrowWaitTimeMillis="); |
| builder.append(maxBorrowWaitTimeMillis); |
| builder.append(", swallowedExceptionListener="); |
| builder.append(swallowedExceptionListener); |
| } |
| |
| |
| } |