| /* |
| * 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 static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assertions.fail; |
| |
| import java.lang.management.ManagementFactory; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Field; |
| import java.nio.charset.UnsupportedCharsetException; |
| import java.time.Duration; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.Objects; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| |
| import org.apache.commons.pool2.BasePooledObjectFactory; |
| 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.SwallowedExceptionListener; |
| import org.apache.commons.pool2.TestBaseObjectPool; |
| import org.apache.commons.pool2.VisitTracker; |
| import org.apache.commons.pool2.VisitTrackerFactory; |
| import org.apache.commons.pool2.Waiter; |
| import org.apache.commons.pool2.WaiterFactory; |
| import org.junit.jupiter.api.AfterEach; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.api.Timeout; |
| |
| /** |
| */ |
| public class TestGenericObjectPool extends TestBaseObjectPool { |
| |
| private class ConcurrentBorrowAndEvictThread extends Thread { |
| private final boolean borrow; |
| public String obj; |
| |
| public ConcurrentBorrowAndEvictThread(final boolean borrow) { |
| this.borrow = borrow; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| if (borrow) { |
| obj = genericObjectPool.borrowObject(); |
| } else { |
| genericObjectPool.evict(); |
| } |
| } catch (final Exception e) { /* Ignore */} |
| } |
| } |
| |
| private static class CreateErrorFactory extends BasePooledObjectFactory<String> { |
| |
| private final Semaphore semaphore = new Semaphore(0); |
| |
| @Override |
| public String create() throws Exception { |
| semaphore.acquire(); |
| throw new UnknownError("wiggle"); |
| } |
| |
| public boolean hasQueuedThreads() { |
| return semaphore.hasQueuedThreads(); |
| } |
| |
| public void release() { |
| semaphore.release(); |
| } |
| |
| @Override |
| public PooledObject<String> wrap(final String obj) { |
| return new DefaultPooledObject<>(obj); |
| } |
| } |
| |
| private static class CreateFailFactory extends BasePooledObjectFactory<String> { |
| |
| private final Semaphore semaphore = new Semaphore(0); |
| |
| @Override |
| public String create() throws Exception { |
| semaphore.acquire(); |
| throw new UnsupportedCharsetException("wibble"); |
| } |
| |
| public boolean hasQueuedThreads() { |
| return semaphore.hasQueuedThreads(); |
| } |
| |
| public void release() { |
| semaphore.release(); |
| } |
| |
| @Override |
| public PooledObject<String> wrap(final String obj) { |
| return new DefaultPooledObject<>(obj); |
| } |
| } |
| |
| private static final class DummyFactory |
| extends BasePooledObjectFactory<Object> { |
| @Override |
| public Object create() throws Exception { |
| return null; |
| } |
| @Override |
| public PooledObject<Object> wrap(final Object value) { |
| return new DefaultPooledObject<>(value); |
| } |
| } |
| |
| private static class EvictionThread<T> extends Thread { |
| |
| private final GenericObjectPool<T> pool; |
| |
| public EvictionThread(final GenericObjectPool<T> pool) { |
| this.pool = pool; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| pool.evict(); |
| } catch (final Exception e) { |
| // Ignore |
| } |
| } |
| } |
| |
| /** |
| * Factory that creates HashSets. Note that this means |
| * 0) All instances are initially equal (not discernible by equals) |
| * 1) Instances are mutable and mutation can cause change in identity / hashcode. |
| */ |
| private static final class HashSetFactory |
| extends BasePooledObjectFactory<HashSet<String>> { |
| @Override |
| public HashSet<String> create() throws Exception { |
| return new HashSet<>(); |
| } |
| @Override |
| public PooledObject<HashSet<String>> wrap(final HashSet<String> value) { |
| return new DefaultPooledObject<>(value); |
| } |
| } |
| |
| /** |
| * Attempts to invalidate an object, swallowing IllegalStateException. |
| */ |
| static class InvalidateThread implements Runnable { |
| private final String obj; |
| private final ObjectPool<String> pool; |
| private boolean done; |
| public InvalidateThread(final ObjectPool<String> pool, final String obj) { |
| this.obj = obj; |
| this.pool = pool; |
| } |
| public boolean complete() { |
| return done; |
| } |
| @Override |
| public void run() { |
| try { |
| pool.invalidateObject(obj); |
| } catch (final IllegalStateException ex) { |
| // Ignore |
| } catch (final Exception ex) { |
| fail("Unexpected exception " + ex.toString()); |
| } finally { |
| done = true; |
| } |
| } |
| } |
| |
| private static class InvalidFactory |
| extends BasePooledObjectFactory<Object> { |
| |
| @Override |
| public Object create() throws Exception { |
| return new Object(); |
| } |
| @Override |
| public boolean validateObject(final PooledObject<Object> obj) { |
| try { |
| Thread.sleep(1000); |
| } catch (final InterruptedException e) { |
| // Ignore |
| } |
| return false; |
| } |
| |
| @Override |
| public PooledObject<Object> wrap(final Object value) { |
| return new DefaultPooledObject<>(value); |
| } |
| } |
| |
| public static class SimpleFactory implements PooledObjectFactory<String> { |
| int makeCounter; |
| |
| int activationCounter; |
| |
| int validateCounter; |
| |
| int activeCount; |
| |
| boolean evenValid = true; |
| |
| boolean oddValid = true; |
| |
| boolean exceptionOnPassivate; |
| |
| boolean exceptionOnActivate; |
| |
| boolean exceptionOnDestroy; |
| |
| boolean enableValidation = true; |
| |
| long destroyLatency; |
| |
| long makeLatency; |
| |
| long validateLatency; |
| |
| int maxTotal = Integer.MAX_VALUE; |
| |
| public SimpleFactory() { |
| this(true); |
| } |
| |
| public SimpleFactory(final boolean valid) { |
| this(valid,valid); |
| } |
| public SimpleFactory(final boolean evalid, final boolean ovalid) { |
| evenValid = evalid; |
| oddValid = ovalid; |
| } |
| @Override |
| public void activateObject(final PooledObject<String> obj) throws Exception { |
| final boolean hurl; |
| final boolean evenTest; |
| final boolean oddTest; |
| final int counter; |
| synchronized(this) { |
| hurl = exceptionOnActivate; |
| evenTest = evenValid; |
| oddTest = oddValid; |
| counter = activationCounter++; |
| } |
| if (hurl && !(counter%2 == 0 ? evenTest : oddTest)) { |
| throw new Exception(); |
| } |
| } |
| @Override |
| public void destroyObject(final PooledObject<String> obj) throws Exception { |
| final long waitLatency; |
| final boolean hurl; |
| synchronized(this) { |
| waitLatency = destroyLatency; |
| hurl = exceptionOnDestroy; |
| } |
| if (waitLatency > 0) { |
| doWait(waitLatency); |
| } |
| synchronized(this) { |
| activeCount--; |
| } |
| if (hurl) { |
| throw new Exception(); |
| } |
| } |
| private void doWait(final long latency) { |
| try { |
| Thread.sleep(latency); |
| } catch (final InterruptedException ex) { |
| // ignore |
| } |
| } |
| public synchronized int getMakeCounter() { |
| return makeCounter; |
| } |
| public synchronized boolean isThrowExceptionOnActivate() { |
| return exceptionOnActivate; |
| } |
| public synchronized boolean isValidationEnabled() { |
| return enableValidation; |
| } |
| @Override |
| public PooledObject<String> makeObject() { |
| final long waitLatency; |
| synchronized(this) { |
| activeCount++; |
| if (activeCount > maxTotal) { |
| throw new IllegalStateException( |
| "Too many active instances: " + activeCount); |
| } |
| waitLatency = makeLatency; |
| } |
| if (waitLatency > 0) { |
| doWait(waitLatency); |
| } |
| final int counter; |
| synchronized(this) { |
| counter = makeCounter++; |
| } |
| return new DefaultPooledObject<>(String.valueOf(counter)); |
| } |
| @Override |
| public void passivateObject(final PooledObject<String> obj) throws Exception { |
| final boolean hurl; |
| synchronized(this) { |
| hurl = exceptionOnPassivate; |
| } |
| if (hurl) { |
| throw new Exception(); |
| } |
| } |
| public synchronized void setDestroyLatency(final long destroyLatency) { |
| this.destroyLatency = destroyLatency; |
| } |
| public synchronized void setEvenValid(final boolean valid) { |
| evenValid = valid; |
| } |
| public synchronized void setMakeLatency(final long makeLatency) { |
| this.makeLatency = makeLatency; |
| } |
| public synchronized void setMaxTotal(final int maxTotal) { |
| this.maxTotal = maxTotal; |
| } |
| public synchronized void setOddValid(final boolean valid) { |
| oddValid = valid; |
| } |
| |
| public synchronized void setThrowExceptionOnActivate(final boolean b) { |
| exceptionOnActivate = b; |
| } |
| |
| public synchronized void setThrowExceptionOnDestroy(final boolean b) { |
| exceptionOnDestroy = b; |
| } |
| |
| public synchronized void setThrowExceptionOnPassivate(final boolean bool) { |
| exceptionOnPassivate = bool; |
| } |
| |
| public synchronized void setValid(final boolean valid) { |
| setEvenValid(valid); |
| setOddValid(valid); |
| } |
| |
| public synchronized void setValidateLatency(final long validateLatency) { |
| this.validateLatency = validateLatency; |
| } |
| |
| public synchronized void setValidationEnabled(final boolean b) { |
| enableValidation = b; |
| } |
| |
| @Override |
| public boolean validateObject(final PooledObject<String> obj) { |
| final boolean validate; |
| final boolean evenTest; |
| final boolean oddTest; |
| final long waitLatency; |
| final int counter; |
| synchronized(this) { |
| validate = enableValidation; |
| evenTest = evenValid; |
| oddTest = oddValid; |
| counter = validateCounter++; |
| waitLatency = validateLatency; |
| } |
| if (waitLatency > 0) { |
| doWait(waitLatency); |
| } |
| if (validate) { |
| return counter%2 == 0 ? evenTest : oddTest; |
| } |
| return true; |
| } |
| } |
| |
| public static class TestEvictionPolicy<T> implements EvictionPolicy<T> { |
| |
| private final AtomicInteger callCount = new AtomicInteger(0); |
| |
| @Override |
| public boolean evict(final EvictionConfig config, final PooledObject<T> underTest, |
| final int idleCount) { |
| return callCount.incrementAndGet() > 1500; |
| } |
| } |
| |
| static class TestThread<T> implements Runnable { |
| |
| /** source of random delay times */ |
| private final java.util.Random _random; |
| |
| /** pool to borrow from */ |
| private final ObjectPool<T> _pool; |
| |
| /** number of borrow attempts */ |
| private final int _iter; |
| |
| /** delay before each borrow attempt */ |
| private final int _startDelay; |
| |
| /** time to hold each borrowed object before returning it */ |
| private final int _holdTime; |
| |
| /** whether or not start and hold time are randomly generated */ |
| private final boolean _randomDelay; |
| |
| /** object expected to be borrowed (fail otherwise) */ |
| private final Object _expectedObject; |
| |
| private volatile boolean _complete; |
| private volatile boolean _failed; |
| private volatile Throwable _error; |
| |
| public TestThread(final ObjectPool<T> pool) { |
| this(pool, 100, 50, true, null); |
| } |
| |
| public TestThread(final ObjectPool<T> pool, final int iter) { |
| this(pool, iter, 50, true, null); |
| } |
| |
| public TestThread(final ObjectPool<T> pool, final int iter, final int delay) { |
| this(pool, iter, delay, true, null); |
| } |
| |
| public TestThread(final ObjectPool<T> pool, final int iter, final int delay, |
| final boolean randomDelay) { |
| this(pool, iter, delay, randomDelay, null); |
| } |
| |
| public TestThread(final ObjectPool<T> pool, final int iter, final int delay, |
| final boolean randomDelay, final Object obj) { |
| this(pool, iter, delay, delay, randomDelay, obj); |
| } |
| |
| public TestThread(final ObjectPool<T> pool, final int iter, final int startDelay, |
| final int holdTime, final boolean randomDelay, final Object obj) { |
| _pool = pool; |
| _iter = iter; |
| _startDelay = startDelay; |
| _holdTime = holdTime; |
| _randomDelay = randomDelay; |
| _random = _randomDelay ? new Random() : null; |
| _expectedObject = obj; |
| } |
| |
| public boolean complete() { |
| return _complete; |
| } |
| |
| public boolean failed() { |
| return _failed; |
| } |
| |
| @Override |
| public void run() { |
| for(int i=0;i<_iter;i++) { |
| final long startDelay = |
| _randomDelay ? (long)_random.nextInt(_startDelay) : _startDelay; |
| final long holdTime = |
| _randomDelay ? (long)_random.nextInt(_holdTime) : _holdTime; |
| try { |
| Thread.sleep(startDelay); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| T obj = null; |
| try { |
| obj = _pool.borrowObject(); |
| } catch(final Exception e) { |
| _error = e; |
| _failed = true; |
| _complete = true; |
| break; |
| } |
| |
| if (_expectedObject != null && !_expectedObject.equals(obj)) { |
| _error = new Throwable("Expected: "+_expectedObject+ " found: "+obj); |
| _failed = true; |
| _complete = true; |
| break; |
| } |
| |
| try { |
| Thread.sleep(holdTime); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| try { |
| _pool.returnObject(obj); |
| } catch(final Exception e) { |
| _error = e; |
| _failed = true; |
| _complete = true; |
| break; |
| } |
| } |
| _complete = true; |
| } |
| } |
| |
| /* |
| * Very simple test thread that just tries to borrow an object from |
| * the provided pool returns it after a wait |
| */ |
| static class WaitingTestThread extends Thread { |
| private final GenericObjectPool<String> _pool; |
| private final long _pause; |
| private Throwable _thrown; |
| |
| private long preBorrowMillis; // just before borrow |
| private long postBorrowMillis; // borrow returned |
| private long postReturnMillis; // after object was returned |
| private long endedMillis; |
| private String objectId; |
| |
| public WaitingTestThread(final GenericObjectPool<String> pool, final long pause) { |
| _pool = pool; |
| _pause = pause; |
| _thrown = null; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| preBorrowMillis = System.currentTimeMillis(); |
| final String obj = _pool.borrowObject(); |
| objectId = obj; |
| postBorrowMillis = System.currentTimeMillis(); |
| Thread.sleep(_pause); |
| _pool.returnObject(obj); |
| postReturnMillis = System.currentTimeMillis(); |
| } catch (final Throwable e) { |
| _thrown = e; |
| } finally{ |
| endedMillis = System.currentTimeMillis(); |
| } |
| } |
| } |
| |
| private static final boolean DISPLAY_THREAD_DETAILS= |
| Boolean.parseBoolean(System.getProperty("TestGenericObjectPool.display.thread.details", "false")); |
| // To pass this to a Maven test, use: |
| // mvn test -DargLine="-DTestGenericObjectPool.display.thread.details=true" |
| // @see https://issues.apache.org/jira/browse/SUREFIRE-121 |
| |
| protected GenericObjectPool<String> genericObjectPool; |
| |
| private SimpleFactory simpleFactory; |
| |
| @SuppressWarnings("deprecation") |
| private void assertConfiguration(final GenericObjectPoolConfig<?> expected, final GenericObjectPool<?> actual) { |
| assertEquals(Boolean.valueOf(expected.getTestOnCreate()), Boolean.valueOf(actual.getTestOnCreate()), |
| "testOnCreate"); |
| assertEquals(Boolean.valueOf(expected.getTestOnBorrow()), Boolean.valueOf(actual.getTestOnBorrow()), |
| "testOnBorrow"); |
| assertEquals(Boolean.valueOf(expected.getTestOnReturn()), Boolean.valueOf(actual.getTestOnReturn()), |
| "testOnReturn"); |
| assertEquals(Boolean.valueOf(expected.getTestWhileIdle()), Boolean.valueOf(actual.getTestWhileIdle()), |
| "testWhileIdle"); |
| assertEquals(Boolean.valueOf(expected.getBlockWhenExhausted()), Boolean.valueOf(actual.getBlockWhenExhausted()), |
| "whenExhaustedAction"); |
| assertEquals(expected.getMaxTotal(), actual.getMaxTotal(), "maxTotal"); |
| assertEquals(expected.getMaxIdle(), actual.getMaxIdle(), "maxIdle"); |
| assertEquals(expected.getMaxWaitMillis(), actual.getMaxWaitMillis(), "maxWaitDuration"); |
| assertEquals(expected.getMaxWaitDuration(), actual.getMaxWaitDuration(), "maxWaitDuration"); |
| assertEquals(expected.getMinEvictableIdleTimeMillis(), actual.getMinEvictableIdleTimeMillis(), |
| "minEvictableIdleTimeMillis"); |
| assertEquals(expected.getMinEvictableIdleTime(), actual.getMinEvictableIdleTime(), |
| "minEvictableIdleTime"); |
| assertEquals(expected.getNumTestsPerEvictionRun(), actual.getNumTestsPerEvictionRun(), |
| "numTestsPerEvictionRun"); |
| assertEquals(expected.getEvictorShutdownTimeoutMillis(), actual.getEvictorShutdownTimeoutMillis(), |
| "evictorShutdownTimeoutMillis"); |
| assertEquals(expected.getEvictorShutdownTimeout(), actual.getEvictorShutdownTimeout(), |
| "evictorShutdownTimeout"); |
| assertEquals(expected.getTimeBetweenEvictionRunsMillis(), actual.getTimeBetweenEvictionRunsMillis(), |
| "timeBetweenEvictionRunsMillis"); |
| assertEquals(expected.getTimeBetweenEvictionRuns(), actual.getTimeBetweenEvictionRuns(), |
| "timeBetweenEvictionRuns"); |
| } |
| |
| private void checkEvict(final boolean lifo) throws Exception { |
| // yea this is hairy but it tests all the code paths in GOP.evict() |
| genericObjectPool.setSoftMinEvictableIdleTime(Duration.ofMillis(10)); |
| genericObjectPool.setMinIdle(2); |
| genericObjectPool.setTestWhileIdle(true); |
| genericObjectPool.setLifo(lifo); |
| genericObjectPool.addObjects(5); |
| genericObjectPool.evict(); |
| simpleFactory.setEvenValid(false); |
| simpleFactory.setOddValid(false); |
| simpleFactory.setThrowExceptionOnActivate(true); |
| genericObjectPool.evict(); |
| genericObjectPool.addObjects(5); |
| simpleFactory.setThrowExceptionOnActivate(false); |
| simpleFactory.setThrowExceptionOnPassivate(true); |
| genericObjectPool.evict(); |
| simpleFactory.setThrowExceptionOnPassivate(false); |
| simpleFactory.setEvenValid(true); |
| simpleFactory.setOddValid(true); |
| Thread.sleep(125); |
| genericObjectPool.evict(); |
| assertEquals(2, genericObjectPool.getNumIdle()); |
| } |
| |
| private void checkEvictionOrder(final boolean lifo) throws Exception { |
| checkEvictionOrderPart1(lifo); |
| tearDown(); |
| setUp(); |
| checkEvictionOrderPart2(lifo); |
| } |
| |
| private void checkEvictionOrderPart1(final boolean lifo) throws Exception { |
| genericObjectPool.setNumTestsPerEvictionRun(2); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100)); |
| genericObjectPool.setLifo(lifo); |
| for (int i = 0; i < 5; i++) { |
| genericObjectPool.addObject(); |
| Thread.sleep(100); |
| } |
| // Order, oldest to youngest, is "0", "1", ...,"4" |
| genericObjectPool.evict(); // Should evict "0" and "1" |
| final Object obj = genericObjectPool.borrowObject(); |
| assertFalse(obj.equals("0"), "oldest not evicted"); |
| assertFalse(obj.equals("1"), "second oldest not evicted"); |
| // 2 should be next out for FIFO, 4 for LIFO |
| assertEquals(lifo ? "4" : "2" , obj,"Wrong instance returned"); |
| } |
| |
| private void checkEvictionOrderPart2(final boolean lifo) throws Exception { |
| // Two eviction runs in sequence |
| genericObjectPool.setNumTestsPerEvictionRun(2); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100)); |
| genericObjectPool.setLifo(lifo); |
| for (int i = 0; i < 5; i++) { |
| genericObjectPool.addObject(); |
| Thread.sleep(100); |
| } |
| genericObjectPool.evict(); // Should evict "0" and "1" |
| genericObjectPool.evict(); // Should evict "2" and "3" |
| final Object obj = genericObjectPool.borrowObject(); |
| assertEquals("4", obj,"Wrong instance remaining in pool"); |
| } |
| |
| private void checkEvictorVisiting(final boolean lifo) throws Exception { |
| VisitTracker<Object> obj; |
| VisitTrackerFactory<Object> trackerFactory = new VisitTrackerFactory<>(); |
| try (GenericObjectPool<VisitTracker<Object>> trackerPool = new GenericObjectPool<>(trackerFactory)) { |
| trackerPool.setNumTestsPerEvictionRun(2); |
| trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1)); |
| trackerPool.setTestWhileIdle(true); |
| trackerPool.setLifo(lifo); |
| trackerPool.setTestOnReturn(false); |
| trackerPool.setTestOnBorrow(false); |
| for (int i = 0; i < 8; i++) { |
| trackerPool.addObject(); |
| } |
| trackerPool.evict(); // Visit oldest 2 - 0 and 1 |
| obj = trackerPool.borrowObject(); |
| trackerPool.returnObject(obj); |
| obj = trackerPool.borrowObject(); |
| trackerPool.returnObject(obj); |
| // borrow, return, borrow, return |
| // FIFO will move 0 and 1 to end |
| // LIFO, 7 out, then in, then out, then in |
| trackerPool.evict(); // Should visit 2 and 3 in either case |
| for (int i = 0; i < 8; i++) { |
| final VisitTracker<Object> tracker = trackerPool.borrowObject(); |
| if (tracker.getId() >= 4) { |
| assertEquals( 0, tracker.getValidateCount(),"Unexpected instance visited " + tracker.getId()); |
| } else { |
| assertEquals( 1, tracker.getValidateCount(), |
| "Instance " + tracker.getId() + " visited wrong number of times."); |
| } |
| } |
| } |
| |
| trackerFactory = new VisitTrackerFactory<>(); |
| try (GenericObjectPool<VisitTracker<Object>> trackerPool = new GenericObjectPool<>(trackerFactory)) { |
| trackerPool.setNumTestsPerEvictionRun(3); |
| trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1)); |
| trackerPool.setTestWhileIdle(true); |
| trackerPool.setLifo(lifo); |
| trackerPool.setTestOnReturn(false); |
| trackerPool.setTestOnBorrow(false); |
| for (int i = 0; i < 8; i++) { |
| trackerPool.addObject(); |
| } |
| trackerPool.evict(); // 0, 1, 2 |
| trackerPool.evict(); // 3, 4, 5 |
| obj = trackerPool.borrowObject(); |
| trackerPool.returnObject(obj); |
| obj = trackerPool.borrowObject(); |
| trackerPool.returnObject(obj); |
| obj = trackerPool.borrowObject(); |
| trackerPool.returnObject(obj); |
| // borrow, return, borrow, return |
| // FIFO 3,4,5,6,7,0,1,2 |
| // LIFO 7,6,5,4,3,2,1,0 |
| // In either case, pointer should be at 6 |
| trackerPool.evict(); |
| // Should hit 6,7,0 - 0 for second time |
| for (int i = 0; i < 8; i++) { |
| final VisitTracker<Object> tracker = trackerPool.borrowObject(); |
| if (tracker.getId() != 0) { |
| assertEquals( 1, tracker.getValidateCount(), |
| "Instance " + tracker.getId() + " visited wrong number of times."); |
| } else { |
| assertEquals( 2, tracker.getValidateCount(), |
| "Instance " + tracker.getId() + " visited wrong number of times."); |
| } |
| } |
| } |
| |
| // Randomly generate a pools with random numTests |
| // and make sure evictor cycles through elements appropriately |
| final int[] smallPrimes = { 2, 3, 5, 7 }; |
| final Random random = new Random(); |
| random.setSeed(System.currentTimeMillis()); |
| for (int i = 0; i < 4; i++) { |
| for (int j = 0; j < 5; j++) { |
| try (GenericObjectPool<VisitTracker<Object>> trackerPool = new GenericObjectPool<>(trackerFactory)) { |
| trackerPool.setNumTestsPerEvictionRun(smallPrimes[i]); |
| trackerPool.setMinEvictableIdleTime(Duration.ofMillis(-1)); |
| trackerPool.setTestWhileIdle(true); |
| trackerPool.setLifo(lifo); |
| trackerPool.setTestOnReturn(false); |
| trackerPool.setTestOnBorrow(false); |
| trackerPool.setMaxIdle(-1); |
| final int instanceCount = 10 + random.nextInt(20); |
| trackerPool.setMaxTotal(instanceCount); |
| for (int k = 0; k < instanceCount; k++) { |
| trackerPool.addObject(); |
| } |
| |
| // Execute a random number of evictor runs |
| final int runs = 10 + random.nextInt(50); |
| for (int k = 0; k < runs; k++) { |
| trackerPool.evict(); |
| } |
| |
| // Number of times evictor should have cycled through the pool |
| final int cycleCount = (runs * trackerPool.getNumTestsPerEvictionRun()) / instanceCount; |
| |
| // Look at elements and make sure they are visited cycleCount |
| // or cycleCount + 1 times |
| VisitTracker<Object> tracker = null; |
| int visitCount = 0; |
| for (int k = 0; k < instanceCount; k++) { |
| tracker = trackerPool.borrowObject(); |
| assertTrue(trackerPool.getNumActive() <= trackerPool.getMaxTotal()); |
| visitCount = tracker.getValidateCount(); |
| assertTrue(visitCount >= cycleCount && visitCount <= cycleCount + 1); |
| } |
| } |
| } |
| } |
| } |
| |
| private BasePooledObjectFactory<String> createDefaultPooledObjectFactory() { |
| return new BasePooledObjectFactory<String>() { |
| @Override |
| public String create() { |
| // fake |
| return null; |
| } |
| |
| @Override |
| public PooledObject<String> wrap(final String obj) { |
| // fake |
| return new DefaultPooledObject<>(obj); |
| } |
| }; |
| } |
| |
| private BasePooledObjectFactory<String> createNullPooledObjectFactory() { |
| return new BasePooledObjectFactory<String>() { |
| @Override |
| public String create() { |
| // fake |
| return null; |
| } |
| |
| @Override |
| public PooledObject<String> wrap(final String obj) { |
| // fake |
| return null; |
| } |
| }; |
| } |
| |
| private BasePooledObjectFactory<String> createSlowObjectFactory(final long elapsedTimeMillis) { |
| return new BasePooledObjectFactory<String>() { |
| @Override |
| public String create() throws Exception { |
| Thread.sleep(elapsedTimeMillis); |
| return "created"; |
| } |
| |
| @Override |
| public PooledObject<String> wrap(final String obj) { |
| // fake |
| return new DefaultPooledObject<>(obj); |
| } |
| }; |
| } |
| |
| @Override |
| protected Object getNthObject(final int n) { |
| return String.valueOf(n); |
| } |
| |
| @Override |
| protected boolean isFifo() { |
| return false; |
| } |
| |
| @Override |
| protected boolean isLifo() { |
| return true; |
| } |
| |
| @Override |
| protected ObjectPool<String> makeEmptyPool(final int minCap) { |
| final GenericObjectPool<String> mtPool = |
| new GenericObjectPool<>(new SimpleFactory()); |
| mtPool.setMaxTotal(minCap); |
| mtPool.setMaxIdle(minCap); |
| return mtPool; |
| } |
| |
| @Override |
| protected ObjectPool<Object> makeEmptyPool( |
| final PooledObjectFactory<Object> fac) { |
| return new GenericObjectPool<>(fac); |
| } |
| |
| /** |
| * Kicks off <numThreads> test threads, each of which will go through |
| * <iterations> borrow-return cycles with random delay times <= delay |
| * in between. |
| */ |
| @SuppressWarnings({ |
| "rawtypes", "unchecked" |
| }) |
| private void runTestThreads(final int numThreads, final int iterations, final int delay, final GenericObjectPool testPool) { |
| final TestThread[] threads = new TestThread[numThreads]; |
| for(int i=0;i<numThreads;i++) { |
| threads[i] = new TestThread<String>(testPool,iterations,delay); |
| final Thread t = new Thread(threads[i]); |
| t.start(); |
| } |
| for(int i=0;i<numThreads;i++) { |
| while(!(threads[i]).complete()) { |
| try { |
| Thread.sleep(500L); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| } |
| if(threads[i].failed()) { |
| fail("Thread " + i + " failed: " + threads[i]._error.toString()); |
| } |
| } |
| } |
| |
| @BeforeEach |
| public void setUp() { |
| simpleFactory = new SimpleFactory(); |
| genericObjectPool = new GenericObjectPool<>(simpleFactory); |
| } |
| |
| @AfterEach |
| public void tearDown() throws Exception { |
| final ObjectName jmxName = genericObjectPool.getJmxName(); |
| final String poolName = Objects.toString(jmxName, null); |
| |
| genericObjectPool.clear(); |
| genericObjectPool.close(); |
| genericObjectPool = null; |
| simpleFactory = null; |
| |
| final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); |
| final Set<ObjectName> result = mbs.queryNames(new ObjectName( |
| "org.apache.commoms.pool2:type=GenericObjectPool,*"), null); |
| // There should be no registered pools at this point |
| final int registeredPoolCount = result.size(); |
| final StringBuilder msg = new StringBuilder("Current pool is: "); |
| msg.append(poolName); |
| msg.append(" Still open pools are: "); |
| for (final ObjectName name : result) { |
| // Clean these up ready for the next test |
| msg.append(name.toString()); |
| msg.append(" created via\n"); |
| msg.append(mbs.getAttribute(name, "CreationStackTrace")); |
| msg.append('\n'); |
| mbs.unregisterMBean(name); |
| } |
| assertEquals( 0, registeredPoolCount,msg.toString()); |
| |
| // Make sure that EvictionTimer executor is shut down |
| final Field evictorExecutorField = EvictionTimer.class.getDeclaredField("executor"); |
| evictorExecutorField.setAccessible(true); |
| assertNull(evictorExecutorField.get(null)); |
| } |
| |
| /** |
| * Check that a pool that starts an evictor, but is never closed does not |
| * leave EvictionTimer executor running. Confirmation check is in teardown. |
| */ |
| @SuppressWarnings("deprecation") |
| @Test |
| public void testAbandonedPool() throws Exception { |
| final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>(); |
| config.setJmxEnabled(false); |
| GenericObjectPool<String> abandoned = new GenericObjectPool<>(simpleFactory, config); |
| abandoned.setTimeBetweenEvictionRuns(Duration.ofMillis(100)); // Starts evictor |
| assertEquals(abandoned.getRemoveAbandonedTimeout(), abandoned.getRemoveAbandonedTimeoutDuration().getSeconds()); |
| |
| // This is ugly, but forces GC to hit the pool |
| final WeakReference<GenericObjectPool<String>> ref = new WeakReference<>(abandoned); |
| abandoned = null; |
| while (ref.get() != null) { |
| System.gc(); |
| Thread.sleep(100); |
| } |
| } |
| |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testAddObject() throws Exception { |
| assertEquals( 0, genericObjectPool.getNumIdle(),"should be zero idle"); |
| genericObjectPool.addObject(); |
| assertEquals( 1, genericObjectPool.getNumIdle(),"should be one idle"); |
| assertEquals( 0, genericObjectPool.getNumActive(),"should be zero active"); |
| final String obj = genericObjectPool.borrowObject(); |
| assertEquals( 0, genericObjectPool.getNumIdle(),"should be zero idle"); |
| assertEquals( 1, genericObjectPool.getNumActive(),"should be one active"); |
| genericObjectPool.returnObject(obj); |
| assertEquals( 1, genericObjectPool.getNumIdle(),"should be one idle"); |
| assertEquals( 0, genericObjectPool.getNumActive(),"should be zero active"); |
| } |
| |
| /* |
| * Note: This test relies on timing for correct execution. There *should* be |
| * enough margin for this to work correctly on most (all?) systems but be |
| * aware of this if you see a failure of this test. |
| */ |
| @SuppressWarnings({ |
| "rawtypes", "unchecked" |
| }) |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testBorrowObjectFairness() throws Exception { |
| |
| final int numThreads = 40; |
| final int maxTotal = 40; |
| |
| final GenericObjectPoolConfig config = new GenericObjectPoolConfig(); |
| config.setMaxTotal(maxTotal); |
| config.setMaxIdle(maxTotal); |
| config.setFairness(true); |
| config.setLifo(false); |
| |
| genericObjectPool = new GenericObjectPool(simpleFactory, config); |
| |
| // Exhaust the pool |
| final String[] objects = new String[maxTotal]; |
| for (int i = 0; i < maxTotal; i++) { |
| objects[i] = genericObjectPool.borrowObject(); |
| } |
| |
| // Start and park threads waiting to borrow objects |
| final TestThread[] threads = new TestThread[numThreads]; |
| for(int i=0;i<numThreads;i++) { |
| threads[i] = new TestThread(genericObjectPool, 1, 0, 2000, false, String.valueOf(i % maxTotal)); |
| final Thread t = new Thread(threads[i]); |
| t.start(); |
| // Short delay to ensure threads start in correct order |
| try { |
| Thread.sleep(10); |
| } catch (final InterruptedException e) { |
| fail(e.toString()); |
| } |
| } |
| |
| // Return objects, other threads should get served in order |
| for (int i = 0; i < maxTotal; i++) { |
| genericObjectPool.returnObject(objects[i]); |
| } |
| |
| // Wait for threads to finish |
| for(int i=0;i<numThreads;i++) { |
| while(!(threads[i]).complete()) { |
| try { |
| Thread.sleep(500L); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| } |
| if(threads[i].failed()) { |
| fail("Thread "+i+" failed: "+threads[i]._error.toString()); |
| } |
| } |
| } |
| |
| /** |
| * On first borrow, first object fails validation, second object is OK. |
| * Subsequent borrows are OK. This was POOL-152. |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testBrokenFactoryShouldNotBlockPool() { |
| final int maxTotal = 1; |
| |
| simpleFactory.setMaxTotal(maxTotal); |
| genericObjectPool.setMaxTotal(maxTotal); |
| genericObjectPool.setBlockWhenExhausted(true); |
| genericObjectPool.setTestOnBorrow(true); |
| |
| // First borrow object will need to create a new object which will fail |
| // validation. |
| String obj = null; |
| Exception ex = null; |
| simpleFactory.setValid(false); |
| try { |
| obj = genericObjectPool.borrowObject(); |
| } catch (final Exception e) { |
| ex = e; |
| } |
| // Failure expected |
| assertNotNull(ex); |
| assertTrue(ex instanceof NoSuchElementException); |
| assertNull(obj); |
| |
| // Configure factory to create valid objects so subsequent borrows work |
| simpleFactory.setValid(true); |
| |
| // Subsequent borrows should be OK |
| try { |
| obj = genericObjectPool.borrowObject(); |
| } catch (final Exception e1) { |
| fail(); |
| } |
| assertNotNull(obj); |
| try { |
| genericObjectPool.returnObject(obj); |
| } catch (final Exception e) { |
| fail(); |
| } |
| } |
| |
| // POOL-259 |
| @Test |
| public void testClientWaitStats() throws Exception { |
| final SimpleFactory factory = new SimpleFactory(); |
| // Give makeObject a little latency |
| factory.setMakeLatency(200); |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>(factory, new GenericObjectPoolConfig<>())) { |
| final String s = pool.borrowObject(); |
| // First borrow waits on create, so wait time should be at least 200 ms |
| // Allow 100ms error in clock times |
| assertTrue(pool.getMaxBorrowWaitTimeMillis() >= 100); |
| assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 100); |
| pool.returnObject(s); |
| pool.borrowObject(); |
| // Second borrow does not have to wait on create, average should be about 100 |
| assertTrue(pool.getMaxBorrowWaitTimeMillis() > 100); |
| assertTrue(pool.getMeanBorrowWaitTimeMillis() < 200); |
| assertTrue(pool.getMeanBorrowWaitTimeMillis() > 20); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testCloseMultiplePools1() { |
| try (final GenericObjectPool<String> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) { |
| genericObjectPool.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND); |
| genericObjectPool2.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND); |
| } |
| genericObjectPool.close(); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testCloseMultiplePools2() throws Exception { |
| try (final GenericObjectPool<String> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) { |
| // Ensure eviction takes a long time, during which time EvictionTimer.executor's queue is empty |
| simpleFactory.setDestroyLatency(1000L); |
| // Ensure there is an object to evict, so that above latency takes effect |
| genericObjectPool.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND); |
| genericObjectPool2.setTimeBetweenEvictionRuns(TestConstants.ONE_MILLISECOND); |
| genericObjectPool.setMinEvictableIdleTime(TestConstants.ONE_MILLISECOND); |
| genericObjectPool2.setMinEvictableIdleTime(TestConstants.ONE_MILLISECOND); |
| genericObjectPool.addObject(); |
| genericObjectPool2.addObject(); |
| // Close both pools |
| } |
| genericObjectPool.close(); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testConcurrentBorrowAndEvict() throws Exception { |
| |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.addObject(); |
| |
| for (int i = 0; i < 5000; i++) { |
| final ConcurrentBorrowAndEvictThread one = |
| new ConcurrentBorrowAndEvictThread(true); |
| final ConcurrentBorrowAndEvictThread two = |
| new ConcurrentBorrowAndEvictThread(false); |
| |
| one.start(); |
| two.start(); |
| one.join(); |
| two.join(); |
| |
| genericObjectPool.returnObject(one.obj); |
| |
| /* Uncomment this for a progress indication |
| if (i % 10 == 0) { |
| System.out.println(i/10); |
| } |
| */ |
| } |
| } |
| |
| /** |
| * POOL-231 - verify that concurrent invalidates of the same object do not |
| * corrupt pool destroyCount. |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testConcurrentInvalidate() throws Exception { |
| // Get allObjects and idleObjects loaded with some instances |
| final int nObjects = 1000; |
| genericObjectPool.setMaxTotal(nObjects); |
| genericObjectPool.setMaxIdle(nObjects); |
| final String[] obj = new String[nObjects]; |
| for (int i = 0; i < nObjects; i++) { |
| obj[i] = genericObjectPool.borrowObject(); |
| } |
| for (int i = 0; i < nObjects; i++) { |
| if (i % 2 == 0) { |
| genericObjectPool.returnObject(obj[i]); |
| } |
| } |
| final int nThreads = 20; |
| final int nIterations = 60; |
| final InvalidateThread[] threads = new InvalidateThread[nThreads]; |
| // Randomly generated list of distinct invalidation targets |
| final ArrayList<Integer> targets = new ArrayList<>(); |
| final Random random = new Random(); |
| for (int j = 0; j < nIterations; j++) { |
| // Get a random invalidation target |
| Integer targ = Integer.valueOf(random.nextInt(nObjects)); |
| while (targets.contains(targ)) { |
| targ = Integer.valueOf(random.nextInt(nObjects)); |
| } |
| targets.add(targ); |
| // Launch nThreads threads all trying to invalidate the target |
| for (int i = 0; i < nThreads; i++) { |
| threads[i] = new InvalidateThread(genericObjectPool, obj[targ.intValue()]); |
| } |
| for (int i = 0; i < nThreads; i++) { |
| new Thread(threads[i]).start(); |
| } |
| boolean done = false; |
| while (!done) { |
| done = true; |
| for (int i = 0; i < nThreads; i++) { |
| done = done && threads[i].complete(); |
| } |
| Thread.sleep(100); |
| } |
| } |
| assertEquals(nIterations, genericObjectPool.getDestroyedCount()); |
| } |
| |
| |
| @Test |
| public void testConstructorNullFactory() { |
| // add dummy assert (won't be invoked because of IAE) to avoid "unused" warning |
| assertThrows(IllegalArgumentException.class, |
| () -> new GenericObjectPool<>(null)); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testConstructors() { |
| |
| // Make constructor arguments all different from defaults |
| final int minIdle = 2; |
| final long maxWait = 3; |
| final int maxIdle = 4; |
| final int maxTotal = 5; |
| final long minEvictableIdleTimeMillis = 6; |
| final int numTestsPerEvictionRun = 7; |
| final boolean testOnBorrow = true; |
| final boolean testOnReturn = true; |
| final boolean testWhileIdle = true; |
| final long timeBetweenEvictionRunsMillis = 8; |
| final boolean blockWhenExhausted = false; |
| final boolean lifo = false; |
| final PooledObjectFactory<Object> dummyFactory = new DummyFactory(); |
| try (GenericObjectPool<Object> dummyPool = new GenericObjectPool<>(dummyFactory)) { |
| assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_IDLE, dummyPool.getMaxIdle()); |
| assertEquals(BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS, dummyPool.getMaxWaitMillis()); |
| assertEquals(GenericObjectPoolConfig.DEFAULT_MIN_IDLE, dummyPool.getMinIdle()); |
| assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL, dummyPool.getMaxTotal()); |
| assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS, |
| dummyPool.getMinEvictableIdleTimeMillis()); |
| assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME, |
| dummyPool.getMinEvictableIdleTime()); |
| assertEquals(BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN, |
| dummyPool.getNumTestsPerEvictionRun()); |
| assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW), |
| Boolean.valueOf(dummyPool.getTestOnBorrow())); |
| assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN), |
| Boolean.valueOf(dummyPool.getTestOnReturn())); |
| assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE), |
| Boolean.valueOf(dummyPool.getTestWhileIdle())); |
| assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS, |
| dummyPool.getTimeBetweenEvictionRunsMillis()); |
| assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS, |
| dummyPool.getTimeBetweenEvictionRuns()); |
| assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED), |
| Boolean.valueOf(dummyPool.getBlockWhenExhausted())); |
| assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_LIFO), Boolean.valueOf(dummyPool.getLifo())); |
| } |
| |
| final GenericObjectPoolConfig<Object> config = new GenericObjectPoolConfig<>(); |
| config.setLifo(lifo); |
| config.setMaxIdle(maxIdle); |
| config.setMinIdle(minIdle); |
| config.setMaxTotal(maxTotal); |
| config.setMaxWaitMillis(maxWait); |
| config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); |
| assertEquals(minEvictableIdleTimeMillis, config.getMinEvictableIdleTime().toMillis()); |
| config.setNumTestsPerEvictionRun(numTestsPerEvictionRun); |
| config.setTestOnBorrow(testOnBorrow); |
| config.setTestOnReturn(testOnReturn); |
| config.setTestWhileIdle(testWhileIdle); |
| config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); |
| assertEquals(timeBetweenEvictionRunsMillis, config.getTimeBetweenEvictionRuns().toMillis()); |
| config.setBlockWhenExhausted(blockWhenExhausted); |
| try (GenericObjectPool<Object> dummyPool = new GenericObjectPool<>(dummyFactory, config)) { |
| assertEquals(maxIdle, dummyPool.getMaxIdle()); |
| assertEquals(maxWait, dummyPool.getMaxWaitMillis()); |
| assertEquals(minIdle, dummyPool.getMinIdle()); |
| assertEquals(maxTotal, dummyPool.getMaxTotal()); |
| assertEquals(minEvictableIdleTimeMillis, dummyPool.getMinEvictableIdleTimeMillis()); |
| assertEquals(numTestsPerEvictionRun, dummyPool.getNumTestsPerEvictionRun()); |
| assertEquals(Boolean.valueOf(testOnBorrow), Boolean.valueOf(dummyPool.getTestOnBorrow())); |
| assertEquals(Boolean.valueOf(testOnReturn), Boolean.valueOf(dummyPool.getTestOnReturn())); |
| assertEquals(Boolean.valueOf(testWhileIdle), Boolean.valueOf(dummyPool.getTestWhileIdle())); |
| assertEquals(timeBetweenEvictionRunsMillis, dummyPool.getTimeBetweenEvictionRunsMillis()); |
| assertEquals(Boolean.valueOf(blockWhenExhausted), Boolean.valueOf(dummyPool.getBlockWhenExhausted())); |
| assertEquals(Boolean.valueOf(lifo), Boolean.valueOf(dummyPool.getLifo())); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testDefaultConfiguration() { |
| assertConfiguration(new GenericObjectPoolConfig<>(),genericObjectPool); |
| } |
| |
| /** |
| * Verifies that when a factory's makeObject produces instances that are not |
| * discernible by equals, the pool can handle them. |
| * |
| * JIRA: POOL-283 |
| */ |
| @Test |
| public void testEqualsIndiscernible() throws Exception { |
| final HashSetFactory factory = new HashSetFactory(); |
| try (final GenericObjectPool<HashSet<String>> pool = new GenericObjectPool<>(factory, |
| new GenericObjectPoolConfig<>())) { |
| final HashSet<String> s1 = pool.borrowObject(); |
| final HashSet<String> s2 = pool.borrowObject(); |
| pool.returnObject(s1); |
| pool.returnObject(s2); |
| } |
| } |
| |
| @Test |
| public void testErrorFactoryDoesNotBlockThreads() throws Exception { |
| |
| final CreateErrorFactory factory = new CreateErrorFactory(); |
| try (final GenericObjectPool<String> createFailFactoryPool = new GenericObjectPool<>(factory)) { |
| |
| createFailFactoryPool.setMaxTotal(1); |
| |
| // Try and borrow the first object from the pool |
| final WaitingTestThread thread1 = new WaitingTestThread(createFailFactoryPool, 0); |
| thread1.start(); |
| |
| // Wait for thread to reach semaphore |
| while (!factory.hasQueuedThreads()) { |
| Thread.sleep(200); |
| } |
| |
| // Try and borrow the second object from the pool |
| final WaitingTestThread thread2 = new WaitingTestThread(createFailFactoryPool, 0); |
| thread2.start(); |
| // Pool will not call factory since maximum number of object creations |
| // are already queued. |
| |
| // Thread 2 will wait on an object being returned to the pool |
| // Give thread 2 a chance to reach this state |
| Thread.sleep(1000); |
| |
| // Release thread1 |
| factory.release(); |
| // Pre-release thread2 |
| factory.release(); |
| |
| // Both threads should now complete. |
| boolean threadRunning = true; |
| int count = 0; |
| while (threadRunning && count < 15) { |
| threadRunning = thread1.isAlive(); |
| threadRunning = thread2.isAlive(); |
| Thread.sleep(200); |
| count++; |
| } |
| assertFalse(thread1.isAlive()); |
| assertFalse(thread2.isAlive()); |
| |
| assertTrue(thread1._thrown instanceof UnknownError); |
| assertTrue(thread2._thrown instanceof UnknownError); |
| } |
| } |
| |
| /** |
| * Tests addObject contention between ensureMinIdle triggered by |
| * the Evictor with minIdle > 0 and borrowObject. |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictAddObjects() throws Exception { |
| simpleFactory.setMakeLatency(300); |
| simpleFactory.setMaxTotal(2); |
| genericObjectPool.setMaxTotal(2); |
| genericObjectPool.setMinIdle(1); |
| genericObjectPool.borrowObject(); // numActive = 1, numIdle = 0 |
| // Create a test thread that will run once and try a borrow after |
| // 150ms fixed delay |
| final TestThread<String> borrower = new TestThread<>(genericObjectPool, 1, 150, false); |
| final Thread borrowerThread = new Thread(borrower); |
| // Set evictor to run in 100 ms - will create idle instance |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100)); |
| borrowerThread.start(); // Off to the races |
| borrowerThread.join(); |
| assertFalse(borrower.failed()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictFIFO() throws Exception { |
| checkEvict(false); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEviction() throws Exception { |
| genericObjectPool.setMaxIdle(500); |
| genericObjectPool.setMaxTotal(500); |
| genericObjectPool.setNumTestsPerEvictionRun(100); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(250)); |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(500)); |
| genericObjectPool.setTestWhileIdle(true); |
| |
| final String[] active = new String[500]; |
| for (int i = 0; i < 500; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| for (int i = 0; i < 500; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| |
| try { Thread.sleep(1000L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle()); |
| |
| for (int i = 0; i < 500; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| for (int i = 0; i < 500; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| |
| try { Thread.sleep(1000L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(600L); } catch(final InterruptedException e) { } |
| assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictionInvalid() throws Exception { |
| |
| try (final GenericObjectPool<Object> invalidFactoryPool = new GenericObjectPool<>(new InvalidFactory())) { |
| |
| invalidFactoryPool.setMaxIdle(1); |
| invalidFactoryPool.setMaxTotal(1); |
| invalidFactoryPool.setTestOnBorrow(false); |
| invalidFactoryPool.setTestOnReturn(false); |
| invalidFactoryPool.setTestWhileIdle(true); |
| invalidFactoryPool.setMinEvictableIdleTime(Duration.ofSeconds(100)); |
| invalidFactoryPool.setNumTestsPerEvictionRun(1); |
| |
| final Object p = invalidFactoryPool.borrowObject(); |
| invalidFactoryPool.returnObject(p); |
| |
| // Run eviction in a separate thread |
| final Thread t = new EvictionThread<>(invalidFactoryPool); |
| t.start(); |
| |
| // Sleep to make sure evictor has started |
| Thread.sleep(300); |
| |
| try { |
| invalidFactoryPool.borrowObject(1); |
| } catch (final NoSuchElementException nsee) { |
| // Ignore |
| } |
| |
| // Make sure evictor has finished |
| Thread.sleep(1000); |
| |
| // Should have an empty pool |
| assertEquals( 0, invalidFactoryPool.getNumIdle(),"Idle count different than expected."); |
| assertEquals( 0, invalidFactoryPool.getNumActive(),"Total count different than expected."); |
| } |
| } |
| |
| /** |
| * Test to make sure evictor visits least recently used objects first, |
| * regardless of FIFO/LIFO. |
| * |
| * JIRA: POOL-86 |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictionOrder() throws Exception { |
| checkEvictionOrder(false); |
| tearDown(); |
| setUp(); |
| checkEvictionOrder(true); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictionPolicy() throws Exception { |
| genericObjectPool.setMaxIdle(500); |
| genericObjectPool.setMaxTotal(500); |
| genericObjectPool.setNumTestsPerEvictionRun(500); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(250)); |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(500)); |
| genericObjectPool.setTestWhileIdle(true); |
| |
| // ClassNotFoundException |
| try { |
| genericObjectPool.setEvictionPolicyClassName(Long.toString(System.currentTimeMillis())); |
| fail("setEvictionPolicyClassName must throw an error if the class name is invalid."); |
| } catch (final IllegalArgumentException e) { |
| // expected |
| } |
| |
| // InstantiationException |
| try { |
| genericObjectPool.setEvictionPolicyClassName(java.io.Serializable.class.getName()); |
| fail("setEvictionPolicyClassName must throw an error if the class name is invalid."); |
| } catch (final IllegalArgumentException e) { |
| // expected |
| } |
| |
| // IllegalAccessException |
| try { |
| genericObjectPool.setEvictionPolicyClassName(java.util.Collections.class.getName()); |
| fail("setEvictionPolicyClassName must throw an error if the class name is invalid."); |
| } catch (final IllegalArgumentException e) { |
| // expected |
| } |
| |
| try { |
| genericObjectPool.setEvictionPolicyClassName(java.lang.String.class.getName()); |
| fail("setEvictionPolicyClassName must throw an error if a class that does not " |
| + "implement EvictionPolicy is specified."); |
| } catch (final IllegalArgumentException e) { |
| // expected |
| } |
| |
| genericObjectPool.setEvictionPolicy(new TestEvictionPolicy<>()); |
| assertEquals(TestEvictionPolicy.class.getName(), genericObjectPool.getEvictionPolicyClassName()); |
| |
| genericObjectPool.setEvictionPolicyClassName(TestEvictionPolicy.class.getName()); |
| assertEquals(TestEvictionPolicy.class.getName(), genericObjectPool.getEvictionPolicyClassName()); |
| |
| final String[] active = new String[500]; |
| for (int i = 0; i < 500; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| for (int i = 0; i < 500; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| |
| // Eviction policy ignores first 1500 attempts to evict and then always |
| // evicts. After 1s, there should have been two runs of 500 tests so no |
| // evictions |
| try { Thread.sleep(1000L); } catch(final InterruptedException e) { } |
| assertEquals( 500, genericObjectPool.getNumIdle(),"Should be 500 idle"); |
| // A further 1s wasn't enough so allow 2s for the evictor to clear out |
| // all of the idle objects. |
| try { Thread.sleep(2000L); } catch(final InterruptedException e) { } |
| assertEquals( 0, genericObjectPool.getNumIdle(),"Should be 0 idle"); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictionSoftMinIdle() throws Exception { |
| class TimeTest extends BasePooledObjectFactory<TimeTest> { |
| private final long createTimeMillis; |
| |
| public TimeTest() { |
| createTimeMillis = System.currentTimeMillis(); |
| } |
| |
| @Override |
| public TimeTest create() throws Exception { |
| return new TimeTest(); |
| } |
| |
| public long getCreateTimeMillis() { |
| return createTimeMillis; |
| } |
| |
| @Override |
| public PooledObject<TimeTest> wrap(final TimeTest value) { |
| return new DefaultPooledObject<>(value); |
| } |
| } |
| |
| try (final GenericObjectPool<TimeTest> timePool = new GenericObjectPool<>(new TimeTest())) { |
| |
| timePool.setMaxIdle(5); |
| timePool.setMaxTotal(5); |
| timePool.setNumTestsPerEvictionRun(5); |
| timePool.setMinEvictableIdleTime(Duration.ofSeconds(3)); |
| timePool.setSoftMinEvictableIdleTime(TestConstants.ONE_SECOND); |
| timePool.setMinIdle(2); |
| |
| final TimeTest[] active = new TimeTest[5]; |
| final Long[] creationTime = new Long[5]; |
| for (int i = 0; i < 5; i++) { |
| active[i] = timePool.borrowObject(); |
| creationTime[i] = Long.valueOf((active[i]).getCreateTimeMillis()); |
| } |
| |
| for (int i = 0; i < 5; i++) { |
| timePool.returnObject(active[i]); |
| } |
| |
| // Soft evict all but minIdle(2) |
| Thread.sleep(1500L); |
| timePool.evict(); |
| assertEquals( 2, timePool.getNumIdle(),"Idle count different than expected."); |
| |
| // Hard evict the rest. |
| Thread.sleep(2000L); |
| timePool.evict(); |
| assertEquals( 0, timePool.getNumIdle(),"Idle count different than expected."); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictionWithNegativeNumTests() throws Exception { |
| // when numTestsPerEvictionRun is negative, it represents a fraction of the idle objects to test |
| genericObjectPool.setMaxIdle(6); |
| genericObjectPool.setMaxTotal(6); |
| genericObjectPool.setNumTestsPerEvictionRun(-2); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50)); |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100)); |
| |
| final String[] active = new String[6]; |
| for (int i = 0; i < 6; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| for (int i = 0; i < 6; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| |
| try { Thread.sleep(100L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() <= 6,"Should at most 6 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(100L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() <= 3,"Should at most 3 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(100L); } catch(final InterruptedException e) { } |
| assertTrue(genericObjectPool.getNumIdle() <= 2,"Should be at most 2 idle, found " + genericObjectPool.getNumIdle()); |
| try { Thread.sleep(100L); } catch(final InterruptedException e) { } |
| assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictLIFO() throws Exception { |
| checkEvict(true); |
| } |
| |
| /** |
| * Verifies that the evictor visits objects in expected order |
| * and frequency. |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testEvictorVisiting() throws Exception { |
| checkEvictorVisiting(true); |
| checkEvictorVisiting(false); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testEvictWhileEmpty() throws Exception { |
| genericObjectPool.evict(); |
| genericObjectPool.evict(); |
| genericObjectPool.close(); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testExceptionOnActivateDuringBorrow() throws Exception { |
| final String obj1 = genericObjectPool.borrowObject(); |
| final String obj2 = genericObjectPool.borrowObject(); |
| genericObjectPool.returnObject(obj1); |
| genericObjectPool.returnObject(obj2); |
| simpleFactory.setThrowExceptionOnActivate(true); |
| simpleFactory.setEvenValid(false); |
| // Activation will now throw every other time |
| // First attempt throws, but loop continues and second succeeds |
| final String obj = genericObjectPool.borrowObject(); |
| assertEquals(1, genericObjectPool.getNumActive()); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| |
| genericObjectPool.returnObject(obj); |
| simpleFactory.setValid(false); |
| // Validation will now fail on activation when borrowObject returns |
| // an idle instance, and then when attempting to create a new instance |
| try { |
| genericObjectPool.borrowObject(); |
| fail("Expecting NoSuchElementException"); |
| } catch (final NoSuchElementException ex) { |
| // expected |
| } |
| assertEquals(0, genericObjectPool.getNumActive()); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testExceptionOnDestroyDuringBorrow() throws Exception { |
| simpleFactory.setThrowExceptionOnDestroy(true); |
| genericObjectPool.setTestOnBorrow(true); |
| genericObjectPool.borrowObject(); |
| simpleFactory.setValid(false); // Make validation fail on next borrow attempt |
| try { |
| genericObjectPool.borrowObject(); |
| fail("Expecting NoSuchElementException"); |
| } catch (final NoSuchElementException ex) { |
| // expected |
| } |
| assertEquals(1, genericObjectPool.getNumActive()); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testExceptionOnDestroyDuringReturn() throws Exception { |
| simpleFactory.setThrowExceptionOnDestroy(true); |
| genericObjectPool.setTestOnReturn(true); |
| final String obj1 = genericObjectPool.borrowObject(); |
| genericObjectPool.borrowObject(); |
| simpleFactory.setValid(false); // Make validation fail |
| genericObjectPool.returnObject(obj1); |
| assertEquals(1, genericObjectPool.getNumActive()); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testExceptionOnPassivateDuringReturn() throws Exception { |
| final String obj = genericObjectPool.borrowObject(); |
| simpleFactory.setThrowExceptionOnPassivate(true); |
| genericObjectPool.returnObject(obj); |
| assertEquals(0,genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| public void testFailingFactoryDoesNotBlockThreads() throws Exception { |
| |
| final CreateFailFactory factory = new CreateFailFactory(); |
| try (final GenericObjectPool<String> createFailFactoryPool = new GenericObjectPool<>(factory)) { |
| |
| createFailFactoryPool.setMaxTotal(1); |
| |
| // Try and borrow the first object from the pool |
| final WaitingTestThread thread1 = new WaitingTestThread(createFailFactoryPool, 0); |
| thread1.start(); |
| |
| // Wait for thread to reach semaphore |
| while (!factory.hasQueuedThreads()) { |
| Thread.sleep(200); |
| } |
| |
| // Try and borrow the second object from the pool |
| final WaitingTestThread thread2 = new WaitingTestThread(createFailFactoryPool, 0); |
| thread2.start(); |
| // Pool will not call factory since maximum number of object creations |
| // are already queued. |
| |
| // Thread 2 will wait on an object being returned to the pool |
| // Give thread 2 a chance to reach this state |
| Thread.sleep(1000); |
| |
| // Release thread1 |
| factory.release(); |
| // Pre-release thread2 |
| factory.release(); |
| |
| // Both threads should now complete. |
| boolean threadRunning = true; |
| int count = 0; |
| while (threadRunning && count < 15) { |
| threadRunning = thread1.isAlive(); |
| threadRunning = thread2.isAlive(); |
| Thread.sleep(200); |
| count++; |
| } |
| assertFalse(thread1.isAlive()); |
| assertFalse(thread2.isAlive()); |
| |
| assertTrue(thread1._thrown instanceof UnsupportedCharsetException); |
| assertTrue(thread2._thrown instanceof UnsupportedCharsetException); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testFIFO() throws Exception { |
| genericObjectPool.setLifo(false); |
| genericObjectPool.addObject(); // "0" |
| genericObjectPool.addObject(); // "1" |
| genericObjectPool.addObject(); // "2" |
| assertEquals( "0", genericObjectPool.borrowObject(),"Oldest"); |
| assertEquals( "1", genericObjectPool.borrowObject(),"Middle"); |
| assertEquals( "2", genericObjectPool.borrowObject(),"Youngest"); |
| final String o = genericObjectPool.borrowObject(); |
| assertEquals( "3", o,"new-3"); |
| genericObjectPool.returnObject(o); |
| assertEquals( o, genericObjectPool.borrowObject(),"returned-3"); |
| assertEquals( "4", genericObjectPool.borrowObject(),"new-4"); |
| } |
| |
| @Test |
| public void testGetFactoryType_DefaultPooledObjectFactory() { |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>(createDefaultPooledObjectFactory())) { |
| assertNotNull((pool.getFactoryType())); |
| } |
| } |
| |
| @Test |
| public void testGetFactoryType_NullPooledObjectFactory() { |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>(createNullPooledObjectFactory())) { |
| assertNotNull((pool.getFactoryType())); |
| } |
| } |
| |
| @Test |
| public void testGetFactoryType_PoolUtilsSynchronizedDefaultPooledFactory() { |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>( |
| PoolUtils.synchronizedPooledFactory(createDefaultPooledObjectFactory()))) { |
| assertNotNull((pool.getFactoryType())); |
| } |
| } |
| |
| @Test |
| public void testGetFactoryType_PoolUtilsSynchronizedNullPooledFactory() { |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>( |
| PoolUtils.synchronizedPooledFactory(createNullPooledObjectFactory()))) { |
| assertNotNull((pool.getFactoryType())); |
| } |
| } |
| |
| @Test |
| public void testGetFactoryType_SynchronizedDefaultPooledObjectFactory() { |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>( |
| new TestSynchronizedPooledObjectFactory<>(createDefaultPooledObjectFactory()))) { |
| assertNotNull((pool.getFactoryType())); |
| } |
| } |
| |
| @Test |
| public void testGetFactoryType_SynchronizedNullPooledObjectFactory() { |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>( |
| new TestSynchronizedPooledObjectFactory<>(createNullPooledObjectFactory()))) { |
| assertNotNull((pool.getFactoryType())); |
| } |
| } |
| |
| /** |
| * Verify that threads waiting on a depleted pool get served when a checked out object is |
| * invalidated. |
| * |
| * JIRA: POOL-240 |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testInvalidateFreesCapacity() throws Exception { |
| final SimpleFactory factory = new SimpleFactory(); |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>(factory)) { |
| pool.setMaxTotal(2); |
| pool.setMaxWaitMillis(500); |
| // Borrow an instance and hold if for 5 seconds |
| final WaitingTestThread thread1 = new WaitingTestThread(pool, 5000); |
| thread1.start(); |
| // Borrow another instance |
| final String obj = pool.borrowObject(); |
| // Launch another thread - will block, but fail in 500 ms |
| final WaitingTestThread thread2 = new WaitingTestThread(pool, 100); |
| thread2.start(); |
| // Invalidate the object borrowed by this thread - should allow thread2 to create |
| Thread.sleep(20); |
| pool.invalidateObject(obj); |
| Thread.sleep(600); // Wait for thread2 to timeout |
| if (thread2._thrown != null) { |
| fail(thread2._thrown.toString()); |
| } |
| } |
| } |
| |
| /** |
| * Ensure the pool is registered. |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testJmxRegistration() { |
| final ObjectName oname = genericObjectPool.getJmxName(); |
| final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); |
| final Set<ObjectName> result = mbs.queryNames(oname, null); |
| assertEquals(1, result.size()); |
| genericObjectPool.jmxUnregister(); |
| |
| final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>(); |
| config.setJmxEnabled(false); |
| try (final GenericObjectPool<String> poolWithoutJmx = new GenericObjectPool<>(simpleFactory, config)) { |
| assertNull(poolWithoutJmx.getJmxName()); |
| config.setJmxEnabled(true); |
| poolWithoutJmx.jmxUnregister(); |
| } |
| |
| config.setJmxNameBase(null); |
| try (final GenericObjectPool<String> poolWithDefaultJmxNameBase = new GenericObjectPool<>(simpleFactory, config)) { |
| assertNotNull(poolWithDefaultJmxNameBase.getJmxName()); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testLIFO() throws Exception { |
| final String o; |
| genericObjectPool.setLifo(true); |
| genericObjectPool.addObject(); // "0" |
| genericObjectPool.addObject(); // "1" |
| genericObjectPool.addObject(); // "2" |
| assertEquals( "2", genericObjectPool.borrowObject(),"Youngest"); |
| assertEquals( "1", genericObjectPool.borrowObject(),"Middle"); |
| assertEquals( "0", genericObjectPool.borrowObject(),"Oldest"); |
| o = genericObjectPool.borrowObject(); |
| assertEquals( "3", o,"new-3"); |
| genericObjectPool.returnObject(o); |
| assertEquals( o, genericObjectPool.borrowObject(),"returned-3"); |
| assertEquals( "4", genericObjectPool.borrowObject(),"new-4"); |
| } |
| |
| /** |
| * Test the following scenario: |
| * Thread 1 borrows an instance |
| * Thread 2 starts to borrow another instance before thread 1 returns its instance |
| * Thread 1 returns its instance while thread 2 is validating its newly created instance |
| * The test verifies that the instance created by Thread 2 is not leaked. |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMakeConcurrentWithReturn() throws Exception { |
| genericObjectPool.setTestOnBorrow(true); |
| simpleFactory.setValid(true); |
| // Borrow and return an instance, with a short wait |
| final WaitingTestThread thread1 = new WaitingTestThread(genericObjectPool, 200); |
| thread1.start(); |
| Thread.sleep(50); // wait for validation to succeed |
| // Slow down validation and borrow an instance |
| simpleFactory.setValidateLatency(400); |
| final String instance = genericObjectPool.borrowObject(); |
| // Now make sure that we have not leaked an instance |
| assertEquals(simpleFactory.getMakeCounter(), genericObjectPool.getNumIdle() + 1); |
| genericObjectPool.returnObject(instance); |
| assertEquals(simpleFactory.getMakeCounter(), genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMaxIdle() throws Exception { |
| genericObjectPool.setMaxTotal(100); |
| genericObjectPool.setMaxIdle(8); |
| final String[] active = new String[100]; |
| for(int i=0;i<100;i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| assertEquals(100,genericObjectPool.getNumActive()); |
| assertEquals(0,genericObjectPool.getNumIdle()); |
| for(int i=0;i<100;i++) { |
| genericObjectPool.returnObject(active[i]); |
| assertEquals(99 - i,genericObjectPool.getNumActive()); |
| assertEquals((i < 8 ? i+1 : 8),genericObjectPool.getNumIdle()); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMaxIdleZero() throws Exception { |
| genericObjectPool.setMaxTotal(100); |
| genericObjectPool.setMaxIdle(0); |
| final String[] active = new String[100]; |
| for(int i=0;i<100;i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| assertEquals(100,genericObjectPool.getNumActive()); |
| assertEquals(0,genericObjectPool.getNumIdle()); |
| for(int i=0;i<100;i++) { |
| genericObjectPool.returnObject(active[i]); |
| assertEquals(99 - i,genericObjectPool.getNumActive()); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| } |
| } |
| |
| /** |
| * Showcasing a possible deadlock situation as reported in POOL-356 |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| @SuppressWarnings("rawtypes") |
| public void testMaxIdleZeroUnderLoad() { |
| // Config |
| final int numThreads = 199; // And main thread makes a round 200. |
| final int numIter = 20; |
| final int delay = 25; |
| final int maxTotal = 10; |
| |
| simpleFactory.setMaxTotal(maxTotal); |
| genericObjectPool.setMaxTotal(maxTotal); |
| genericObjectPool.setBlockWhenExhausted(true); |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(-1)); |
| |
| // this is important to trigger POOL-356 |
| genericObjectPool.setMaxIdle(0); |
| |
| // Start threads to borrow objects |
| final TestThread[] threads = new TestThread[numThreads]; |
| for(int i=0;i<numThreads;i++) { |
| // Factor of 2 on iterations so main thread does work whilst other |
| // threads are running. Factor of 2 on delay so average delay for |
| // other threads == actual delay for main thread |
| threads[i] = new TestThread<>(genericObjectPool, numIter * 2, delay * 2); |
| final Thread t = new Thread(threads[i]); |
| t.start(); |
| } |
| // Give the threads a chance to start doing some work |
| try { |
| Thread.sleep(100); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| |
| for (int i = 0; i < numIter; i++) { |
| String obj = null; |
| try { |
| try { |
| Thread.sleep(delay); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| obj = genericObjectPool.borrowObject(); |
| // Under load, observed _numActive > _maxTotal |
| if (genericObjectPool.getNumActive() > genericObjectPool.getMaxTotal()) { |
| throw new IllegalStateException("Too many active objects"); |
| } |
| try { |
| Thread.sleep(delay); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| } catch (final Exception e) { |
| // Shouldn't happen |
| e.printStackTrace(); |
| fail("Exception on borrow"); |
| } finally { |
| if (obj != null) { |
| try { |
| genericObjectPool.returnObject(obj); |
| } catch (final Exception e) { |
| // Ignore |
| } |
| } |
| } |
| } |
| |
| for(int i=0;i<numThreads;i++) { |
| while(!(threads[i]).complete()) { |
| try { |
| Thread.sleep(500L); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| } |
| if(threads[i].failed()) { |
| threads[i]._error.printStackTrace(); |
| fail("Thread "+i+" failed: "+threads[i]._error.toString()); |
| } |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMaxTotal() throws Exception { |
| genericObjectPool.setMaxTotal(3); |
| genericObjectPool.setBlockWhenExhausted(false); |
| |
| genericObjectPool.borrowObject(); |
| genericObjectPool.borrowObject(); |
| genericObjectPool.borrowObject(); |
| try { |
| genericObjectPool.borrowObject(); |
| fail("Expected NoSuchElementException"); |
| } catch(final NoSuchElementException e) { |
| // expected |
| } |
| } |
| |
| /** |
| * Verifies that maxTotal is not exceeded when factory destroyObject |
| * has high latency, testOnReturn is set and there is high incidence of |
| * validation failures. |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMaxTotalInvariant() { |
| final int maxTotal = 15; |
| simpleFactory.setEvenValid(false); // Every other validation fails |
| simpleFactory.setDestroyLatency(100); // Destroy takes 100 ms |
| simpleFactory.setMaxTotal(maxTotal); // (makes - destroys) bound |
| simpleFactory.setValidationEnabled(true); |
| genericObjectPool.setMaxTotal(maxTotal); |
| genericObjectPool.setMaxIdle(-1); |
| genericObjectPool.setTestOnReturn(true); |
| genericObjectPool.setMaxWaitMillis(1000L); |
| runTestThreads(5, 10, 50, genericObjectPool); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| @SuppressWarnings("rawtypes") |
| public void testMaxTotalUnderLoad() { |
| // Config |
| final int numThreads = 199; // And main thread makes a round 200. |
| final int numIter = 20; |
| final int delay = 25; |
| final int maxTotal = 10; |
| |
| simpleFactory.setMaxTotal(maxTotal); |
| genericObjectPool.setMaxTotal(maxTotal); |
| genericObjectPool.setBlockWhenExhausted(true); |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(-1)); |
| |
| // Start threads to borrow objects |
| final TestThread[] threads = new TestThread[numThreads]; |
| for(int i=0;i<numThreads;i++) { |
| // Factor of 2 on iterations so main thread does work whilst other |
| // threads are running. Factor of 2 on delay so average delay for |
| // other threads == actual delay for main thread |
| threads[i] = new TestThread<>(genericObjectPool, numIter * 2, delay * 2); |
| final Thread t = new Thread(threads[i]); |
| t.start(); |
| } |
| // Give the threads a chance to start doing some work |
| try { |
| Thread.sleep(5000); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| |
| for (int i = 0; i < numIter; i++) { |
| String obj = null; |
| try { |
| try { |
| Thread.sleep(delay); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| obj = genericObjectPool.borrowObject(); |
| // Under load, observed _numActive > _maxTotal |
| if (genericObjectPool.getNumActive() > genericObjectPool.getMaxTotal()) { |
| throw new IllegalStateException("Too many active objects"); |
| } |
| try { |
| Thread.sleep(delay); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| } catch (final Exception e) { |
| // Shouldn't happen |
| e.printStackTrace(); |
| fail("Exception on borrow"); |
| } finally { |
| if (obj != null) { |
| try { |
| genericObjectPool.returnObject(obj); |
| } catch (final Exception e) { |
| // Ignore |
| } |
| } |
| } |
| } |
| |
| for (int i = 0; i < numThreads; i++) { |
| while(!(threads[i]).complete()) { |
| try { |
| Thread.sleep(500L); |
| } catch(final InterruptedException e) { |
| // ignored |
| } |
| } |
| if(threads[i].failed()) { |
| fail("Thread " + i + " failed: " + threads[i]._error.toString()); |
| } |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMaxTotalZero() throws Exception { |
| genericObjectPool.setMaxTotal(0); |
| genericObjectPool.setBlockWhenExhausted(false); |
| |
| try { |
| genericObjectPool.borrowObject(); |
| fail("Expected NoSuchElementException"); |
| } catch(final NoSuchElementException e) { |
| // expected |
| } |
| } |
| |
| /* |
| * Test multi-threaded pool access. |
| * Multiple threads, but maxTotal only allows half the threads to succeed. |
| * |
| * This test was prompted by Continuum build failures in the Commons DBCP test case: |
| * TestPerUserPoolDataSource.testMultipleThreads2() |
| * Let's see if the this fails on Continuum too! |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMaxWaitMultiThreaded() throws Exception { |
| final long maxWait = 500; // wait for connection |
| final long holdTime = 2 * maxWait; // how long to hold connection |
| final int threads = 10; // number of threads to grab the object initially |
| genericObjectPool.setBlockWhenExhausted(true); |
| genericObjectPool.setMaxWaitMillis(maxWait); |
| genericObjectPool.setMaxTotal(threads); |
| // Create enough threads so half the threads will have to wait |
| final WaitingTestThread[] wtt = new WaitingTestThread[threads * 2]; |
| for (int i = 0; i < wtt.length; i++) { |
| wtt[i] = new WaitingTestThread(genericObjectPool,holdTime); |
| } |
| final long originMillis = System.currentTimeMillis() - 1000; |
| for (final WaitingTestThread element : wtt) { |
| element.start(); |
| } |
| int failed = 0; |
| for (final WaitingTestThread element : wtt) { |
| element.join(); |
| if (element._thrown != null){ |
| failed++; |
| } |
| } |
| if (DISPLAY_THREAD_DETAILS || wtt.length/2 != failed){ |
| System.out.println( |
| "MaxWait: " + maxWait + |
| " HoldTime: " + holdTime + |
| " MaxTotal: " + threads + |
| " Threads: " + wtt.length + |
| " Failed: " + failed |
| ); |
| for (final WaitingTestThread wt : wtt) { |
| System.out.println( |
| "PreBorrow: " + (wt.preBorrowMillis - originMillis) + |
| " PostBorrow: " + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - originMillis : -1) + |
| " BorrowTime: " + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - wt.preBorrowMillis : -1) + |
| " PostReturn: " + (wt.postReturnMillis != 0 ? wt.postReturnMillis - originMillis : -1) + |
| " Ended: " + (wt.endedMillis - originMillis) + |
| " ObjId: " + wt.objectId |
| ); |
| } |
| } |
| assertEquals(wtt.length / 2, failed,"Expected half the threads to fail"); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMinIdle() throws Exception { |
| genericObjectPool.setMaxIdle(500); |
| genericObjectPool.setMinIdle(5); |
| genericObjectPool.setMaxTotal(10); |
| genericObjectPool.setNumTestsPerEvictionRun(0); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50)); |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100)); |
| genericObjectPool.setTestWhileIdle(true); |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle()); |
| |
| final String[] active = new String[5]; |
| active[0] = genericObjectPool.borrowObject(); |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle()); |
| |
| for(int i=1 ; i<5 ; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle()); |
| |
| for(int i=0 ; i<5 ; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testMinIdleMaxTotal() throws Exception { |
| genericObjectPool.setMaxIdle(500); |
| genericObjectPool.setMinIdle(5); |
| genericObjectPool.setMaxTotal(10); |
| genericObjectPool.setNumTestsPerEvictionRun(0); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(50)); |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100)); |
| genericObjectPool.setTestWhileIdle(true); |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle()); |
| |
| final String[] active = new String[10]; |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle()); |
| |
| for (int i = 0; i < 5; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle()); |
| |
| for(int i = 0 ; i < 5 ; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle()); |
| |
| for (int i = 0; i < 10; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(0, genericObjectPool.getNumIdle(), "Should be 0 idle, found " + genericObjectPool.getNumIdle()); |
| |
| for (int i = 0; i < 10; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| |
| try { Thread.sleep(150L); } catch(final InterruptedException e) { } |
| assertEquals(10, genericObjectPool.getNumIdle(), "Should be 10 idle, found " + genericObjectPool.getNumIdle()); |
| } |
| |
| /** |
| * Verifies that returning an object twice (without borrow in between) causes ISE |
| * but does not re-validate or re-passivate the instance. |
| * |
| * JIRA: POOL-285 |
| */ |
| @Test |
| public void testMultipleReturn() throws Exception { |
| final WaiterFactory<String> factory = new WaiterFactory<>(0, 0, 0, 0, 0, 0); |
| try (final GenericObjectPool<Waiter> pool = new GenericObjectPool<>(factory)) { |
| pool.setTestOnReturn(true); |
| final Waiter waiter = pool.borrowObject(); |
| pool.returnObject(waiter); |
| assertEquals(1, waiter.getValidationCount()); |
| assertEquals(1, waiter.getPassivationCount()); |
| try { |
| pool.returnObject(waiter); |
| fail("Expecting IllegalStateException from multiple return"); |
| } catch (final IllegalStateException ex) { |
| // Exception is expected, now check no repeat validation/passivation |
| assertEquals(1, waiter.getValidationCount()); |
| assertEquals(1, waiter.getPassivationCount()); |
| } |
| } |
| } |
| |
| // POOL-248 |
| @Test |
| public void testMultipleReturnOfSameObject() throws Exception { |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>(simpleFactory, new GenericObjectPoolConfig<>())) { |
| |
| assertEquals(0, pool.getNumActive()); |
| assertEquals(0, pool.getNumIdle()); |
| |
| final String obj = pool.borrowObject(); |
| |
| assertEquals(1, pool.getNumActive()); |
| assertEquals(0, pool.getNumIdle()); |
| |
| pool.returnObject(obj); |
| |
| assertEquals(0, pool.getNumActive()); |
| assertEquals(1, pool.getNumIdle()); |
| |
| assertThrows(IllegalStateException.class, |
| () -> pool.returnObject(obj)); |
| |
| assertEquals(0, pool.getNumActive()); |
| assertEquals(1, pool.getNumIdle()); |
| } |
| } |
| |
| /** |
| * Verifies that when a borrowed object is mutated in a way that does not |
| * preserve equality and hashcode, the pool can recognized it on return. |
| * |
| * JIRA: POOL-284 |
| */ |
| @Test |
| public void testMutable() throws Exception { |
| final HashSetFactory factory = new HashSetFactory(); |
| try (final GenericObjectPool<HashSet<String>> pool = new GenericObjectPool<>(factory, |
| new GenericObjectPoolConfig<>())) { |
| final HashSet<String> s1 = pool.borrowObject(); |
| final HashSet<String> s2 = pool.borrowObject(); |
| s1.add("One"); |
| s2.add("One"); |
| pool.returnObject(s1); |
| pool.returnObject(s2); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testNegativeMaxTotal() throws Exception { |
| genericObjectPool.setMaxTotal(-1); |
| genericObjectPool.setBlockWhenExhausted(false); |
| final String obj = genericObjectPool.borrowObject(); |
| assertEquals(getNthObject(0),obj); |
| genericObjectPool.returnObject(obj); |
| } |
| |
| /** |
| * Verifies that concurrent threads never "share" instances |
| */ |
| @Test |
| public void testNoInstanceOverlap() { |
| final int maxTotal = 5; |
| final int numThreads = 100; |
| final int delay = 1; |
| final int iterations = 1000; |
| final AtomicIntegerFactory factory = new AtomicIntegerFactory(); |
| try (final GenericObjectPool<AtomicInteger> pool = new GenericObjectPool<>(factory)) { |
| pool.setMaxTotal(maxTotal); |
| pool.setMaxIdle(maxTotal); |
| pool.setTestOnBorrow(true); |
| pool.setBlockWhenExhausted(true); |
| pool.setMaxWaitMillis(-1); |
| runTestThreads(numThreads, iterations, delay, pool); |
| assertEquals(0, pool.getDestroyedByBorrowValidationCount()); |
| } |
| } |
| |
| /** |
| * POOL-376 |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testNoInvalidateNPE() throws Exception { |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.setTestOnCreate(true); |
| genericObjectPool.setMaxWaitMillis(-1); |
| final String obj = genericObjectPool.borrowObject(); |
| // Make validation fail - this will cause create() to return null |
| simpleFactory.setValid(false); |
| // Create a take waiter |
| final WaitingTestThread wtt = new WaitingTestThread(genericObjectPool, 200); |
| wtt.start(); |
| // Give wtt time to start |
| Thread.sleep(200); |
| genericObjectPool.invalidateObject(obj); |
| // Now allow create to succeed so waiter can be served |
| simpleFactory.setValid(true); |
| } |
| |
| public void testPreparePool() throws Exception { |
| genericObjectPool.setMinIdle(1); |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.preparePool(); |
| assertEquals(1, genericObjectPool.getNumIdle()); |
| final String obj = genericObjectPool.borrowObject(); |
| genericObjectPool.preparePool(); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| genericObjectPool.setMinIdle(0); |
| genericObjectPool.returnObject(obj); |
| genericObjectPool.preparePool(); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| } |
| |
| @Test/* maxWaitMillis x2 + padding */ |
| @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS) |
| public void testReturnBorrowObjectWithingMaxWaitMillis() throws Exception { |
| final long maxWaitMillis = 500; |
| |
| try (final GenericObjectPool<String> createSlowObjectFactoryPool = new GenericObjectPool<>( |
| createSlowObjectFactory(60000))) { |
| createSlowObjectFactoryPool.setMaxTotal(1); |
| createSlowObjectFactoryPool.setMaxWaitMillis(maxWaitMillis); |
| |
| // thread1 tries creating a slow object to make pool full. |
| final WaitingTestThread thread1 = new WaitingTestThread(createSlowObjectFactoryPool, 0); |
| thread1.start(); |
| |
| // Wait for thread1's reaching to create(). |
| Thread.sleep(100); |
| |
| // another one tries borrowObject. It should return within maxWaitMillis. |
| try { |
| createSlowObjectFactoryPool.borrowObject(maxWaitMillis); |
| fail("borrowObject must fail due to timeout by maxWaitMillis"); |
| } catch (final NoSuchElementException e) { |
| // ignore |
| } |
| |
| assertTrue(thread1.isAlive()); |
| } |
| } |
| |
| /** |
| * This is the test case for POOL-263. It is disabled since it will always |
| * pass without artificial delay being injected into GOP.returnObject() and |
| * a way to this hasn't currently been found that doesn't involve |
| * polluting the GOP implementation. The artificial delay needs to be |
| * inserted just before the final call to isLifo() in the returnObject() |
| * method. |
| */ |
| //@Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testReturnObject() throws Exception { |
| |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.setMaxIdle(-1); |
| final String active = genericObjectPool.borrowObject(); |
| |
| assertEquals(1, genericObjectPool.getNumActive()); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| |
| final Thread t = new Thread(() -> genericObjectPool.close()); |
| t.start(); |
| |
| genericObjectPool.returnObject(active); |
| |
| // Wait for the close() thread to complete |
| while (t.isAlive()) { |
| Thread.sleep(50); |
| } |
| |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testSetConfig() throws Exception { |
| final GenericObjectPoolConfig<String> expected = new GenericObjectPoolConfig<>(); |
| assertConfiguration(expected,genericObjectPool); |
| expected.setMaxTotal(2); |
| expected.setMaxIdle(3); |
| expected.setMaxWaitMillis(5L); |
| expected.setMinEvictableIdleTime(Duration.ofMillis(7L)); |
| expected.setNumTestsPerEvictionRun(9); |
| expected.setTestOnCreate(true); |
| expected.setTestOnBorrow(true); |
| expected.setTestOnReturn(true); |
| expected.setTestWhileIdle(true); |
| expected.setTimeBetweenEvictionRuns(Duration.ofMillis(11L)); |
| expected.setBlockWhenExhausted(false); |
| genericObjectPool.setConfig(expected); |
| assertConfiguration(expected,genericObjectPool); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testSettersAndGetters() throws Exception { |
| { |
| // The object receives an Exception during its creation to prevent |
| // memory leaks. See BaseGenericObjectPool constructor for more details. |
| assertEquals(false, "".equals(genericObjectPool.getCreationStackTrace())); |
| } |
| { |
| assertEquals(0, genericObjectPool.getBorrowedCount()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getReturnedCount()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getCreatedCount()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getDestroyedCount()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getDestroyedByEvictorCount()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getDestroyedByBorrowValidationCount()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getMeanActiveTimeMillis()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getMeanIdleTimeMillis()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getMeanBorrowWaitTimeMillis()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getMaxBorrowWaitTimeMillis()); |
| } |
| { |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| } |
| { |
| genericObjectPool.setMaxTotal(123); |
| assertEquals(123,genericObjectPool.getMaxTotal()); |
| } |
| { |
| genericObjectPool.setMaxIdle(12); |
| assertEquals(12,genericObjectPool.getMaxIdle()); |
| } |
| { |
| genericObjectPool.setMaxWaitMillis(1234L); |
| assertEquals(1234L,genericObjectPool.getMaxWaitMillis()); |
| } |
| { |
| genericObjectPool.setMinEvictableIdleTimeMillis(12345L); |
| assertEquals(12345L,genericObjectPool.getMinEvictableIdleTimeMillis()); |
| assertEquals(12345L,genericObjectPool.getMinEvictableIdleTime().toMillis()); |
| } |
| { |
| genericObjectPool.setNumTestsPerEvictionRun(11); |
| assertEquals(11,genericObjectPool.getNumTestsPerEvictionRun()); |
| } |
| { |
| genericObjectPool.setTestOnBorrow(true); |
| assertTrue(genericObjectPool.getTestOnBorrow()); |
| genericObjectPool.setTestOnBorrow(false); |
| assertFalse(genericObjectPool.getTestOnBorrow()); |
| } |
| { |
| genericObjectPool.setTestOnReturn(true); |
| assertTrue(genericObjectPool.getTestOnReturn()); |
| genericObjectPool.setTestOnReturn(false); |
| assertFalse(genericObjectPool.getTestOnReturn()); |
| } |
| { |
| genericObjectPool.setTestWhileIdle(true); |
| assertTrue(genericObjectPool.getTestWhileIdle()); |
| genericObjectPool.setTestWhileIdle(false); |
| assertFalse(genericObjectPool.getTestWhileIdle()); |
| } |
| { |
| genericObjectPool.setTimeBetweenEvictionRunsMillis(11235L); |
| assertEquals(11235L,genericObjectPool.getTimeBetweenEvictionRunsMillis()); |
| assertEquals(11235L,genericObjectPool.getTimeBetweenEvictionRuns().toMillis()); |
| } |
| { |
| genericObjectPool.setSoftMinEvictableIdleTimeMillis(12135L); |
| assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleTimeMillis()); |
| assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleTime().toMillis()); |
| } |
| { |
| genericObjectPool.setBlockWhenExhausted(true); |
| assertTrue(genericObjectPool.getBlockWhenExhausted()); |
| genericObjectPool.setBlockWhenExhausted(false); |
| assertFalse(genericObjectPool.getBlockWhenExhausted()); |
| } |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testStartAndStopEvictor() throws Exception { |
| // set up pool without evictor |
| genericObjectPool.setMaxIdle(6); |
| genericObjectPool.setMaxTotal(6); |
| genericObjectPool.setNumTestsPerEvictionRun(6); |
| genericObjectPool.setMinEvictableIdleTime(Duration.ofMillis(100)); |
| |
| for (int j = 0; j < 2; j++) { |
| // populate the pool |
| { |
| final String[] active = new String[6]; |
| for (int i = 0; i < 6; i++) { |
| active[i] = genericObjectPool.borrowObject(); |
| } |
| for (int i = 0; i < 6; i++) { |
| genericObjectPool.returnObject(active[i]); |
| } |
| } |
| |
| // note that it stays populated |
| assertEquals(6,genericObjectPool.getNumIdle(),"Should have 6 idle"); |
| |
| // start the evictor |
| genericObjectPool.setTimeBetweenEvictionRuns(Duration.ofMillis(50)); |
| |
| // wait a second (well, .2 seconds) |
| try { Thread.sleep(200L); } catch(final InterruptedException e) { } |
| |
| // assert that the evictor has cleared out the pool |
| assertEquals(0,genericObjectPool.getNumIdle(),"Should have 0 idle"); |
| |
| // stop the evictor |
| genericObjectPool.startEvictor(Duration.ZERO); |
| } |
| } |
| |
| @Test |
| public void testSwallowedExceptionListener() { |
| genericObjectPool.setSwallowedExceptionListener(null); // must simply return |
| final List<Exception> swallowedExceptions = new ArrayList<>(); |
| /* |
| * A simple listener, that will throw a OOM on 3rd exception. |
| */ |
| final SwallowedExceptionListener listener = e -> { |
| if (swallowedExceptions.size() == 2) { |
| throw new OutOfMemoryError(); |
| } |
| swallowedExceptions.add(e); |
| }; |
| genericObjectPool.setSwallowedExceptionListener(listener); |
| |
| final Exception e1 = new Exception(); |
| final Exception e2 = new ArrayIndexOutOfBoundsException(); |
| |
| genericObjectPool.swallowException(e1); |
| genericObjectPool.swallowException(e2); |
| |
| try { |
| genericObjectPool.swallowException(e1); |
| fail("Not supposed to get here"); |
| } catch (final OutOfMemoryError oom) { |
| // expected |
| } |
| |
| assertEquals(2, swallowedExceptions.size()); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testThreaded1() throws Exception { |
| genericObjectPool.setMaxTotal(15); |
| genericObjectPool.setMaxIdle(15); |
| genericObjectPool.setMaxWaitMillis(1000L); |
| runTestThreads(20, 100, 50, genericObjectPool); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testTimeoutNoLeak() throws Exception { |
| genericObjectPool.setMaxTotal(2); |
| genericObjectPool.setMaxWaitMillis(10); |
| genericObjectPool.setBlockWhenExhausted(true); |
| final String obj = genericObjectPool.borrowObject(); |
| final String obj2 = genericObjectPool.borrowObject(); |
| try { |
| genericObjectPool.borrowObject(); |
| fail("Expecting NoSuchElementException"); |
| } catch (final NoSuchElementException ex) { |
| // expected |
| } |
| genericObjectPool.returnObject(obj2); |
| genericObjectPool.returnObject(obj); |
| |
| genericObjectPool.borrowObject(); |
| genericObjectPool.borrowObject(); |
| } |
| |
| /** |
| * Tests POOL-361 |
| */ |
| @Test |
| public void testValidateOnCreate() throws Exception { |
| genericObjectPool.setTestOnCreate(true); |
| genericObjectPool.addObject(); |
| assertEquals(1, simpleFactory.validateCounter); |
| } |
| |
| /** |
| * Tests POOL-361 |
| */ |
| @Test |
| public void testValidateOnCreateFailure() throws Exception { |
| genericObjectPool.setTestOnCreate(true); |
| genericObjectPool.setTestOnBorrow(false); |
| genericObjectPool.setMaxTotal(2); |
| simpleFactory.setValid(false); |
| // Make sure failed validations do not leak capacity |
| genericObjectPool.addObject(); |
| genericObjectPool.addObject(); |
| assertEquals(0, genericObjectPool.getNumIdle()); |
| assertEquals(0, genericObjectPool.getNumActive()); |
| simpleFactory.setValid(true); |
| final String obj = genericObjectPool.borrowObject(); |
| assertNotNull(obj); |
| genericObjectPool.addObject(); |
| // Should have one idle, one out now |
| assertEquals(1, genericObjectPool.getNumIdle()); |
| assertEquals(1, genericObjectPool.getNumActive()); |
| } |
| |
| /** |
| * Verify that threads waiting on a depleted pool get served when a returning object fails |
| * validation. |
| * |
| * JIRA: POOL-240 |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testValidationFailureOnReturnFreesCapacity() throws Exception { |
| final SimpleFactory factory = new SimpleFactory(); |
| factory.setValid(false); // Validate will always fail |
| factory.setValidationEnabled(true); |
| try (final GenericObjectPool<String> pool = new GenericObjectPool<>(factory)) { |
| pool.setMaxTotal(2); |
| pool.setMaxWaitMillis(1500); |
| pool.setTestOnReturn(true); |
| pool.setTestOnBorrow(false); |
| // Borrow an instance and hold if for 5 seconds |
| final WaitingTestThread thread1 = new WaitingTestThread(pool, 5000); |
| thread1.start(); |
| // Borrow another instance and return it after 500 ms (validation will fail) |
| final WaitingTestThread thread2 = new WaitingTestThread(pool, 500); |
| thread2.start(); |
| Thread.sleep(50); |
| // Try to borrow an object |
| final String obj = pool.borrowObject(); |
| pool.returnObject(obj); |
| } |
| } |
| |
| // POOL-276 |
| @Test |
| public void testValidationOnCreateOnly() throws Exception { |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.setTestOnCreate(true); |
| genericObjectPool.setTestOnBorrow(false); |
| genericObjectPool.setTestOnReturn(false); |
| genericObjectPool.setTestWhileIdle(false); |
| |
| final String o1 = genericObjectPool.borrowObject(); |
| assertEquals("0", o1); |
| final Timer t = new Timer(); |
| t.schedule( |
| new TimerTask() { |
| @Override |
| public void run() { |
| genericObjectPool.returnObject(o1); |
| } |
| }, 3000); |
| |
| final String o2 = genericObjectPool.borrowObject(); |
| assertEquals("0", o2); |
| |
| assertEquals(1, simpleFactory.validateCounter); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testWhenExhaustedBlock() throws Exception { |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.setBlockWhenExhausted(true); |
| genericObjectPool.setMaxWaitMillis(10L); |
| final String obj1 = genericObjectPool.borrowObject(); |
| assertNotNull(obj1); |
| try { |
| genericObjectPool.borrowObject(); |
| fail("Expected NoSuchElementException"); |
| } catch(final NoSuchElementException e) { |
| // expected |
| } |
| genericObjectPool.returnObject(obj1); |
| genericObjectPool.close(); |
| } |
| |
| /** |
| * POOL-189 |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testWhenExhaustedBlockClosePool() throws Exception { |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.setBlockWhenExhausted(true); |
| genericObjectPool.setMaxWaitMillis(-1); |
| final Object obj1 = genericObjectPool.borrowObject(); |
| |
| // Make sure an object was obtained |
| assertNotNull(obj1); |
| |
| // Create a separate thread to try and borrow another object |
| final WaitingTestThread wtt = new WaitingTestThread(genericObjectPool, 200); |
| wtt.start(); |
| // Give wtt time to start |
| Thread.sleep(200); |
| |
| // close the pool (Bug POOL-189) |
| genericObjectPool.close(); |
| |
| // Give interrupt time to take effect |
| Thread.sleep(200); |
| |
| // Check thread was interrupted |
| assertTrue(wtt._thrown instanceof InterruptedException); |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testWhenExhaustedBlockInterrupt() throws Exception { |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.setBlockWhenExhausted(true); |
| genericObjectPool.setMaxWaitMillis(-1); |
| final String obj1 = genericObjectPool.borrowObject(); |
| |
| // Make sure on object was obtained |
| assertNotNull(obj1); |
| |
| // Create a separate thread to try and borrow another object |
| final WaitingTestThread wtt = new WaitingTestThread(genericObjectPool, 200000); |
| wtt.start(); |
| // Give wtt time to start |
| Thread.sleep(200); |
| wtt.interrupt(); |
| |
| // Give interrupt time to take effect |
| Thread.sleep(200); |
| |
| // Check thread was interrupted |
| assertTrue(wtt._thrown instanceof InterruptedException); |
| |
| // Return object to the pool |
| genericObjectPool.returnObject(obj1); |
| |
| // Bug POOL-162 - check there is now an object in the pool |
| genericObjectPool.setMaxWaitMillis(10L); |
| String obj2 = null; |
| try { |
| obj2 = genericObjectPool.borrowObject(); |
| assertNotNull(obj2); |
| } catch(final NoSuchElementException e) { |
| // Not expected |
| fail("NoSuchElementException not expected"); |
| } |
| genericObjectPool.returnObject(obj2); |
| genericObjectPool.close(); |
| |
| } |
| |
| @Test |
| @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) |
| public void testWhenExhaustedFail() throws Exception { |
| genericObjectPool.setMaxTotal(1); |
| genericObjectPool.setBlockWhenExhausted(false); |
| final String obj1 = genericObjectPool.borrowObject(); |
| assertNotNull(obj1); |
| try { |
| genericObjectPool.borrowObject(); |
| fail("Expected NoSuchElementException"); |
| } catch(final NoSuchElementException e) { |
| // expected |
| } |
| genericObjectPool.returnObject(obj1); |
| assertEquals(1, genericObjectPool.getNumIdle()); |
| genericObjectPool.close(); |
| } |
| |
| } |