This is an automated email from the ASF dual-hosted git repository. psteitz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-pool.git
The following commit(s) were added to refs/heads/master by this push: new 7c63f29b JIRA:POOL-407. Add ResilientPooledObjectFactory. new b50d1626 Merge branch 'master' of https://github.com/apache/commons-pool 7c63f29b is described below commit 7c63f29b9b90b3fbbc829d3ce0b0e4f98c832351 Author: Phil Steitz <phil.ste...@gmail.com> AuthorDate: Fri May 31 10:53:12 2024 -0700 JIRA:POOL-407. Add ResilientPooledObjectFactory. --- src/changes/changes.xml | 4 + .../pool3/impl/ResilientPooledObjectFactory.java | 379 ++++++++++++++++++ .../commons/pool3/impl/TestGenericObjectPool.java | 429 ++++++++++++--------- .../impl/TestResilientPooledObjectFactory.java | 339 ++++++++++++++++ 4 files changed, 971 insertions(+), 180 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index f6d3603b..d9756758 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -60,6 +60,10 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="update">Bump org.ow2.asm:asm-util from 9.5 to 9.7 #252, #294.</action> <action type="update" dev="ggregory" due-to="Gary Gregory">Bump commons-lang3 from 3.13.0 to 3.14.0.</action> <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump org.apache.bcel:bcel from 6.7.0 to 6.9.0 #263, #306.</action> + <!-- FIX --> + <action dev="psteitz" type="fix" issue="POOL-407"> + Add ReslientPooledObjectFactory to provide resilence against factory outages. + </action> </release> <release version="2.12.0" date="2023-MM-DD" description="This is a feature and maintenance release (Java 8 or above)."> <!-- FIX --> diff --git a/src/main/java/org/apache/commons/pool3/impl/ResilientPooledObjectFactory.java b/src/main/java/org/apache/commons/pool3/impl/ResilientPooledObjectFactory.java new file mode 100644 index 00000000..6cc8877e --- /dev/null +++ b/src/main/java/org/apache/commons/pool3/impl/ResilientPooledObjectFactory.java @@ -0,0 +1,379 @@ +/* + * 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.pool3.impl; + +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.commons.pool3.PooledObject; +import org.apache.commons.pool3.PooledObjectFactory; + +/** + * Wraps a PooledObjectFactory, extending to provide resilient features. + * <p> + * Maintains a cicrular log of makeObject calls and makes strategy-based + * decisions on whether to keep trying proactively to create objects. + * Decisions use data in the makeObject log and information reported by + * the pool that the factory is attached to. + * + * @param <T> Type of object managed by the factory + * @param <E> Type of exception that the factory may throw + */ +public class ResilientPooledObjectFactory<T, E extends Exception> implements PooledObjectFactory<T, E> { + private static final int DEFAULT_LOG_SIZE = 10; + private static final Duration DEFAULT_DELAY = Duration.ofSeconds(1); + private static final Duration DEFAULT_LOOK_BACK = Duration.ofMinutes(5); + private static final Duration DEFAULT_TIME_BETWEEN_CHECKS = Duration.ofSeconds(10); + /** Wrapped factory */ + private final PooledObjectFactory<T, E> factory; + /** GOP that factory is attached to. */ + private GenericObjectPool<T, E> pool; + /** Size of the circular log of makeObject events */ + private int logSize; + /** Duration of time window for statistics */ + private final Duration lookBack; + /** Circular log of makeObject events */ + private final ConcurrentLinkedQueue<MakeEvent> makeObjectLog = new ConcurrentLinkedQueue<>(); + /** Time of last factory failure */ + private Instant downStart; + /** Time factory last returned to "up" state. */ + private Instant upStart; + /** Exception counts */ + @SuppressWarnings("rawtypes") + private ConcurrentHashMap<Class, Integer> exceptionCounts = new ConcurrentHashMap<>(); + /** Whether or not the factory is "up" */ + private boolean up = true; + /** Whether or not the monitor thread is running */ + private boolean monitoring = false; + /** Time to wait between object creations by the adder thread */ + private final Duration delay; + /** Time between monitor checks */ + private Duration timeBetweenChecks = Duration.ofSeconds(10); + + /** Adder thread */ + private Adder adder = null; + + /** + * Construct a ResilientPooledObjectFactory from a factory with specified + * parameters. + * + * @param factory PooledObjectFactory to wrap + * @param logSize length of the makeObject log + * @param delay time to wait between object creations by the adder + * thread + * @param lookBack length of time over which metrics are kept + * @param timeBetweenChecks time between checks by the monitor thread + */ + public ResilientPooledObjectFactory(PooledObjectFactory<T, E> factory, + int logSize, Duration delay, Duration lookBack, Duration timeBetweenChecks) { + this.logSize = logSize; + this.factory = factory; + this.delay = delay; + this.lookBack = lookBack; + this.timeBetweenChecks = timeBetweenChecks; + } + + /** + * Construct a ResilientPooledObjectFactory from a factory and pool, using + * defaults for logSize, delay, and lookBack. + * + * @param factory PooledObjectFactory to wrap + */ + public ResilientPooledObjectFactory(PooledObjectFactory<T, E> factory) { + this(factory, DEFAULT_LOG_SIZE, DEFAULT_DELAY, DEFAULT_LOOK_BACK, DEFAULT_TIME_BETWEEN_CHECKS); + } + + public void setPool(GenericObjectPool<T, E> pool) { + this.pool = pool; + } + + public void setTimeBetweenChecks(Duration timeBetweenChecks) { + this.timeBetweenChecks = timeBetweenChecks; + } + + public void setLogSize(int logSize) { + this.logSize = logSize; + } + + /** + * Delegate to the wrapped factory, but log the makeObject call. + */ + @Override + public PooledObject<T> makeObject() throws E { + final MakeEvent makeEvent = new MakeEvent(); + try { + PooledObject<T> obj = factory.makeObject(); + makeEvent.setSuccess(!PooledObject.isNull(obj)); + return obj; + } catch (Throwable t) { + makeEvent.setSuccess(false); + exceptionCounts.put(t.getClass(), exceptionCounts.getOrDefault(t, 0) + 1); + throw t; + } finally { + makeEvent.end(); + makeObjectLog.add(makeEvent); + } + } + + // Delegate all other methods to the wrapped factory. + + @Override + public void destroyObject(PooledObject<T> p) throws E { + factory.destroyObject(p); + } + + @Override + public boolean validateObject(PooledObject<T> p) { + return factory.validateObject(p); + } + + @Override + public void activateObject(PooledObject<T> p) throws E { + factory.activateObject(p); + } + + @Override + public void passivateObject(PooledObject<T> p) throws E { + factory.passivateObject(p); + } + + /** + * Default implementation considers the factory down as soon as a single + * makeObject call fails and considers it back up if the last logSize makes have + * succeeded. + * <p> + * Sets downStart to time of the first failure found in makeObjectLog and + * upStart to the time when logSize consecutive makes have succeeded. + * <p> + * When a failure is observed, the adder thread is started if the pool + * is not closed and has take waiters. + * <p> + * Removes the oldest event from the log if it is full. + * + */ + protected void runChecks() { + boolean upOverLog = true; + // 1. If the log is full, remove the oldest (first) event. + // + // 2. Walk the event log. If we find a failure, set downStart, set up to false + // and start the adder thread. + // + // 3. If the log contains only successes, if up is false, set upStart and up to + // true + // and kill the adder thread. + while (makeObjectLog.size() > logSize) { + makeObjectLog.poll(); + } + for (MakeEvent makeEvent : makeObjectLog) { + if (!makeEvent.isSuccess()) { + upOverLog = false; + downStart = Instant.now(); + up = false; + if (pool.getNumWaiters() > 0 && !pool.isClosed() && adder == null) { + adder = new Adder(); + adder.start(); + } + } + + } + if (upOverLog && !up) { + // Kill adder thread and set up to true + upStart = Instant.now(); + up = true; + adder.kill(); + adder = null; + } + } + + /** + * @return true if the factory is considered "up". + */ + public boolean isUp() { + return up; + } + + /** + * @return true if the adder is running + */ + public boolean isAdderRunning() { + return adder != null && adder.isRunning(); + } + + /** + * @return true if the monitor is running + */ + public boolean isMonitorRunning() { + return monitoring; + } + + public void startMonitor(Duration timeBetweenChecks) { + this.timeBetweenChecks = timeBetweenChecks; + startMonitor(); + } + + public void startMonitor() { + monitoring = true; + new Monitor().start(); + } + + public void stopMonitor() { + monitoring = false; + } + + /** + * Adder thread that adds objects to the pool, waiting for a fixed delay between + * adds. + * <p> + * The adder thread will stop under any of the following conditions: + * <ul> + * <li>The pool is closed.</li> + * <li>The factory is down.</li> + * <li>The pool is full.</li> + * <li>The pool has no waiters.</li> + * </ul> + */ + class Adder extends Thread { + private boolean killed = false; + private boolean running = false; + private int MAX_FAILURES = 5; + private int failures = 0; + + @Override + public void run() { + running = true; + while (!up && !killed && !pool.isClosed()) { + try { + pool.addObject(); + if (pool.getNumWaiters() == 0 || pool.getNumActive() + pool.getNumIdle() == pool.getMaxTotal()) { + kill(); + } + } catch (Throwable e) { + failures++; + if (failures > MAX_FAILURES) { + kill(); + } + } finally { + // Wait for delay + try { + Thread.sleep(delay.toMillis()); + } catch (InterruptedException e) { + // Ignore + } catch (Throwable e) { + killed = true; + running = false; + throw (e); + } + } + } + killed = true; + running = false; + } + + public void start() { + if (killed) { + killed = false; + } + run(); + } + + public boolean isRunning() { + return running; + } + + public void kill() { + killed = true; + } + } + + /** + * Record of a makeObject event. + */ + static class MakeEvent { + private final Instant startTime; + private Instant endTime; + private boolean success; + private Throwable exception; + private String message; + + /** + * Constructor set statTime to now. + */ + public MakeEvent() { + startTime = Instant.now(); + } + + /** + * @return the time the makeObject call ended + */ + public Instant getEndTime() { + return endTime; + } + + public void end() { + this.endTime = Instant.now(); + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public Throwable getException() { + return exception; + } + + public void setException(Throwable exception) { + this.exception = exception; + } + + public Instant getStartTime() { + return startTime; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + } + + class Monitor extends Thread { + @Override + public void run() { + while (monitoring && !pool.isClosed()) { + runChecks(); + try { + Thread.sleep(timeBetweenChecks.toMillis()); + } catch (InterruptedException e) { + monitoring = false; + } catch (Throwable e) { + monitoring = false; + throw (e); + } + } + monitoring = false; + } + } +} diff --git a/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java b/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java index 3a3066eb..f09656bc 100644 --- a/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java +++ b/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java @@ -148,6 +148,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { public Object create() { return null; } + @Override public PooledObject<Object> wrap(final Object value) { return new DefaultPooledObject<>(value); @@ -173,9 +174,10 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } /** - * 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 / hash code. + * 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 / hash + * code. */ private static final class HashSetFactory extends BasePooledObjectFactory<HashSet<String>, RuntimeException> { @@ -183,6 +185,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { public HashSet<String> create() { return new HashSet<>(); } + @Override public PooledObject<HashSet<String>> wrap(final HashSet<String> value) { return new DefaultPooledObject<>(value); @@ -196,13 +199,16 @@ public class TestGenericObjectPool extends TestBaseObjectPool { private final String obj; private final ObjectPool<String, ? extends Exception> pool; private boolean done; + public InvalidateThread(final ObjectPool<String, ? extends Exception> pool, final String obj) { this.obj = obj; this.pool = pool; } + public boolean complete() { return done; } + @Override public void run() { try { @@ -224,6 +230,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { public Object create() { return new Object(); } + @Override public boolean validateObject(final PooledObject<Object> obj) { Waiter.sleepQuietly(1000); @@ -272,7 +279,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } public SimpleFactory(final boolean valid) { - this(valid,valid); + this(valid, valid); } public SimpleFactory(final boolean evalid, final boolean ovalid) { @@ -292,7 +299,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { oddTest = oddValid; counter = activationCounter++; } - if (hurl && !(counter%2 == 0 ? evenTest : oddTest)) { + if (hurl && !(counter % 2 == 0 ? evenTest : oddTest)) { throw new TestException(); } } @@ -339,7 +346,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { activeCount++; if (activeCount > maxTotal) { throw new IllegalStateException( - "Too many active instances: " + activeCount); + "Too many active instances: " + activeCount); } waitLatency = makeLatency; } @@ -436,7 +443,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { throw new RuntimeException("validation failed"); } if (validate) { - return counter%2 == 0 ? evenTest : oddTest; + return counter % 2 == 0 ? evenTest : oddTest; } return true; } @@ -503,15 +510,15 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } public TestThread(final ObjectPool<T, E> pool, final int iter, final int startDelay, - final int holdTime, final boolean randomDelay, final Object obj) { - this.pool = pool; - this.iter = iter; - this.startDelay = startDelay; - this.holdTime = holdTime; - this.randomDelay = randomDelay; - this.random = this.randomDelay ? new Random() : null; - this.expectedObject = obj; - } + final int holdTime, final boolean randomDelay, final Object obj) { + this.pool = pool; + this.iter = iter; + this.startDelay = startDelay; + this.holdTime = holdTime; + this.randomDelay = randomDelay; + this.random = this.randomDelay ? new Random() : null; + this.expectedObject = obj; + } public boolean complete() { return complete; @@ -568,7 +575,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { private Throwable thrown; private long preBorrowMillis; // just before borrow - private long postBorrowMillis; // borrow returned + private long postBorrowMillis; // borrow returned private long postReturnMillis; // after object was returned private long endedMillis; private String objectId; @@ -591,14 +598,14 @@ public class TestGenericObjectPool extends TestBaseObjectPool { postReturnMillis = System.currentTimeMillis(); } catch (final Throwable e) { thrown = e; - } finally{ + } finally { endedMillis = System.currentTimeMillis(); } } } - private static final boolean DISPLAY_THREAD_DETAILS= - Boolean.getBoolean("TestGenericObjectPool.display.thread.details"); + private static final boolean DISPLAY_THREAD_DETAILS = Boolean + .getBoolean("TestGenericObjectPool.display.thread.details"); // 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 @@ -674,7 +681,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { assertNotEquals("0", obj, "oldest not evicted"); assertNotEquals("1", obj, "second oldest not evicted"); // 2 should be next out for FIFO, 4 for LIFO - assertEquals(lifo ? "4" : "2" , obj,"Wrong instance returned"); + assertEquals(lifo ? "4" : "2", obj, "Wrong instance returned"); } private void checkEvictionOrderPart2(final boolean lifo) throws Exception { @@ -689,13 +696,14 @@ public class TestGenericObjectPool extends TestBaseObjectPool { 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"); + 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>,RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) { + try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>( + trackerFactory)) { trackerPool.setNumTestsPerEvictionRun(2); trackerPool.setMinEvictableIdleDuration(Duration.ofMillis(-1)); trackerPool.setTestWhileIdle(true); @@ -717,16 +725,17 @@ public class TestGenericObjectPool extends TestBaseObjectPool { 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()); + assertEquals(0, tracker.getValidateCount(), "Unexpected instance visited " + tracker.getId()); } else { - assertEquals( 1, tracker.getValidateCount(), + assertEquals(1, tracker.getValidateCount(), "Instance " + tracker.getId() + " visited wrong number of times."); } } } trackerFactory = new VisitTrackerFactory<>(); - try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) { + try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>( + trackerFactory)) { trackerPool.setNumTestsPerEvictionRun(3); trackerPool.setMinEvictableIdleDuration(Duration.ofMillis(-1)); trackerPool.setTestWhileIdle(true); @@ -753,10 +762,10 @@ public class TestGenericObjectPool extends TestBaseObjectPool { for (int i = 0; i < 8; i++) { final VisitTracker<Object> tracker = trackerPool.borrowObject(); if (tracker.getId() != 0) { - assertEquals( 1, tracker.getValidateCount(), + assertEquals(1, tracker.getValidateCount(), "Instance " + tracker.getId() + " visited wrong number of times."); } else { - assertEquals( 2, tracker.getValidateCount(), + assertEquals(2, tracker.getValidateCount(), "Instance " + tracker.getId() + " visited wrong number of times."); } } @@ -769,7 +778,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { random.setSeed(System.currentTimeMillis()); for (int i = 0; i < 4; i++) { for (int j = 0; j < 5; j++) { - try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>(trackerFactory)) { + try (GenericObjectPool<VisitTracker<Object>, RuntimeException> trackerPool = new GenericObjectPool<>( + trackerFactory)) { trackerPool.setNumTestsPerEvictionRun(smallPrimes[i]); trackerPool.setMinEvictableIdleDuration(Duration.ofMillis(-1)); trackerPool.setTestWhileIdle(true); @@ -839,7 +849,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { }; } - private BasePooledObjectFactory<String, InterruptedException> createSlowObjectFactory(final long elapsedTimeMillis) { + private BasePooledObjectFactory<String, InterruptedException> createSlowObjectFactory( + final long elapsedTimeMillis) { return new BasePooledObjectFactory<String, InterruptedException>() { @Override public String create() throws InterruptedException { @@ -874,8 +885,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { protected ObjectPool<String, TestException> makeEmptyPool(final int minCap) { final GenericObjectPool<String, TestException> mtPool = new GenericObjectPool<>(new SimpleFactory()); mtPool.setMaxTotal(minCap); - mtPool.setMaxIdle(minCap); - return mtPool; + mtPool.setMaxIdle(minCap); + return mtPool; } @Override @@ -888,7 +899,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { * <iterations> borrow-return cycles with random delay times <= delay * in between. */ - private <T, E extends Exception> void runTestThreads(final int numThreads, final int iterations, final int delay, final GenericObjectPool<T, E> testPool) { + private <T, E extends Exception> void runTestThreads(final int numThreads, final int iterations, final int delay, + final GenericObjectPool<T, E> testPool) { final TestThread<T, E>[] threads = new TestThread[numThreads]; for (int i = 0; i < numThreads; i++) { threads[i] = new TestThread<>(testPool, iterations, delay); @@ -922,7 +934,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { simpleFactory = null; final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); - final Set<ObjectName> result = mbs.queryNames(new ObjectName("org.apache.commoms.pool3:type=GenericObjectPool,*"), null); + final Set<ObjectName> result = mbs + .queryNames(new ObjectName("org.apache.commoms.pool3: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: "); @@ -947,12 +960,15 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } /** - * Check that a pool that starts an evictor, but is never closed does not leave EvictionTimer executor running. Confirmation check is in + * Check that a pool that starts an evictor, but is never closed does not leave + * EvictionTimer executor running. Confirmation check is in * {@link #tearDown()}. * - * @throws TestException Custom exception - * @throws InterruptedException if any thread has interrupted the current thread. The <em>interrupted status</em> of the current thread is cleared when this - * exception is thrown. + * @throws TestException Custom exception + * @throws InterruptedException if any thread has interrupted the current + * thread. The <em>interrupted status</em> of the + * current thread is cleared when this + * exception is thrown. */ @SuppressWarnings("deprecation") @Test @@ -975,16 +991,16 @@ public class TestGenericObjectPool extends TestBaseObjectPool { @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) public void testAddObject() throws Exception { - assertEquals( 0, genericObjectPool.getNumIdle(),"should be zero idle"); + 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"); + 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"); + 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"); + assertEquals(1, genericObjectPool.getNumIdle(), "should be one idle"); + assertEquals(0, genericObjectPool.getNumActive(), "should be zero active"); } @Test @@ -1005,7 +1021,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { * aware of this if you see a failure of this test. */ @SuppressWarnings({ - "rawtypes", "unchecked" + "rawtypes", "unchecked" }) @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) @@ -1030,7 +1046,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { // Start and park threads waiting to borrow objects final TestThread[] threads = new TestThread[numThreads]; - for(int i=0;i<numThreads;i++) { + 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(); @@ -1059,9 +1075,12 @@ public class TestGenericObjectPool extends TestBaseObjectPool { // Borrow final String object = genericObjectPool.borrowObject(); final PooledObject<String> po = genericObjectPool.getPooledObject(object); - // In the initial state, all instants are the creation instant: last borrow, last use, last return. - // In the initial state, the active duration is the time between "now" and the creation time. - // In the initial state, the idle duration is the time between "now" and the last return, which is the creation time. + // In the initial state, all instants are the creation instant: last borrow, + // last use, last return. + // In the initial state, the active duration is the time between "now" and the + // creation time. + // In the initial state, the idle duration is the time between "now" and the + // last return, which is the creation time. // But... this PO might have already been used in other tests in this class. final Instant lastBorrowInstant1 = po.getLastBorrowInstant(); @@ -1072,13 +1091,15 @@ public class TestGenericObjectPool extends TestBaseObjectPool { assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastReturnInstant1)); assertThat(po.getCreateInstant(), lessThanOrEqualTo(lastUsedInstant1)); - // Sleep MUST be "long enough" to detect that more than 0 milliseconds have elapsed. + // Sleep MUST be "long enough" to detect that more than 0 milliseconds have + // elapsed. // Need an API in Java 8 to get the clock granularity. Thread.sleep(200); assertFalse(po.getActiveDuration().isNegative()); assertFalse(po.getActiveDuration().isZero()); - // We use greaterThanOrEqualTo instead of equal because "now" many be different when each argument is evaluated. + // We use greaterThanOrEqualTo instead of equal because "now" many be different + // when each argument is evaluated. assertThat(1L, lessThanOrEqualTo(2L)); // sanity check assertThat(Duration.ZERO, lessThanOrEqualTo(Duration.ZERO.plusNanos(1))); // sanity check assertThat(po.getActiveDuration(), lessThanOrEqualTo(po.getIdleDuration())); @@ -1110,6 +1131,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { /** * On first borrow, first object fails validation, second object is OK. * Subsequent borrows are OK. This was POOL-152. + * * @throws Exception */ @Test @@ -1152,7 +1174,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { final SimpleFactory factory = new SimpleFactory(); // Give makeObject a little latency factory.setMakeLatency(200); - try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(factory, new GenericObjectPoolConfig<>())) { + try (final GenericObjectPool<String, TestException> 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 @@ -1170,7 +1193,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) public void testCloseMultiplePools1() { - try (final GenericObjectPool<String, TestException> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) { + try (final GenericObjectPool<String, TestException> genericObjectPool2 = new GenericObjectPool<>( + simpleFactory)) { genericObjectPool.setDurationBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION); genericObjectPool2.setDurationBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION); } @@ -1180,8 +1204,10 @@ public class TestGenericObjectPool extends TestBaseObjectPool { @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) public void testCloseMultiplePools2() throws Exception { - try (final GenericObjectPool<String, TestException> genericObjectPool2 = new GenericObjectPool<>(simpleFactory)) { - // Ensure eviction takes a long time, during which time EvictionTimer.executor's queue is empty + try (final GenericObjectPool<String, TestException> 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.setDurationBetweenEvictionRuns(TestConstants.ONE_MILLISECOND_DURATION); @@ -1203,10 +1229,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.addObject(); for (int i = 0; i < 5000; i++) { - final ConcurrentBorrowAndEvictThread one = - new ConcurrentBorrowAndEvictThread(true); - final ConcurrentBorrowAndEvictThread two = - new ConcurrentBorrowAndEvictThread(false); + final ConcurrentBorrowAndEvictThread one = new ConcurrentBorrowAndEvictThread(true); + final ConcurrentBorrowAndEvictThread two = new ConcurrentBorrowAndEvictThread(false); one.start(); two.start(); @@ -1215,11 +1239,12 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.returnObject(one.obj); - /* Uncomment this for a progress indication - if (i % 10 == 0) { - System.out.println(i/10); - } - */ + /* + * Uncomment this for a progress indication + * if (i % 10 == 0) { + * System.out.println(i/10); + * } + */ } } @@ -1308,13 +1333,20 @@ public class TestGenericObjectPool extends TestBaseObjectPool { assertEquals(BaseObjectPoolConfig.DEFAULT_MAX_WAIT, dummyPool.getMaxWaitDuration()); assertEquals(GenericObjectPoolConfig.DEFAULT_MIN_IDLE, dummyPool.getMinIdle()); assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL, dummyPool.getMaxTotal()); - assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION, dummyPool.getMinEvictableIdleDuration()); - 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_DURATION_BETWEEN_EVICTION_RUNS, dummyPool.getDurationBetweenEvictionRuns()); - assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED), Boolean.valueOf(dummyPool.getBlockWhenExhausted())); + assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION, + dummyPool.getMinEvictableIdleDuration()); + 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_DURATION_BETWEEN_EVICTION_RUNS, + dummyPool.getDurationBetweenEvictionRuns()); + assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED), + Boolean.valueOf(dummyPool.getBlockWhenExhausted())); assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_LIFO), Boolean.valueOf(dummyPool.getLifo())); } @@ -1352,7 +1384,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) public void testDefaultConfiguration() { - assertConfiguration(new GenericObjectPoolConfig<>(),genericObjectPool); + assertConfiguration(new GenericObjectPoolConfig<>(), genericObjectPool); } /** @@ -1377,7 +1409,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { public void testErrorFactoryDoesNotBlockThreads() throws Exception { final CreateErrorFactory factory = new CreateErrorFactory(); - try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>(factory)) { + try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>( + factory)) { createFailFactoryPool.setMaxTotal(1); @@ -1442,7 +1475,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { final Thread borrowerThread = new Thread(borrower); // Set evictor to run in 100 ms - will create idle instance genericObjectPool.setDurationBetweenEvictionRuns(Duration.ofMillis(100)); - borrowerThread.start(); // Off to the races + borrowerThread.start(); // Off to the races borrowerThread.join(); assertFalse(borrower.failed()); } @@ -1472,17 +1505,22 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } Waiter.sleepQuietly(1000L); - assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 500, + "Should be less than 500 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 400, + "Should be less than 400 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 300, + "Should be less than 300 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 200, + "Should be less than 200 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 100, + "Should be less than 100 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle()); + assertEquals(0, genericObjectPool.getNumIdle(), "Should be zero idle, found " + genericObjectPool.getNumIdle()); for (int i = 0; i < 500; i++) { active[i] = genericObjectPool.borrowObject(); @@ -1492,24 +1530,30 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } Waiter.sleepQuietly(1000L); - assertTrue(genericObjectPool.getNumIdle() < 500,"Should be less than 500 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 500, + "Should be less than 500 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 400,"Should be less than 400 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 400, + "Should be less than 400 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 300,"Should be less than 300 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 300, + "Should be less than 300 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 200,"Should be less than 200 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 200, + "Should be less than 200 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertTrue(genericObjectPool.getNumIdle() < 100,"Should be less than 100 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() < 100, + "Should be less than 100 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(600L); - assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle()); + 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, RuntimeException> invalidFactoryPool = new GenericObjectPool<>(new InvalidFactory())) { + try (final GenericObjectPool<Object, RuntimeException> invalidFactoryPool = new GenericObjectPool<>( + new InvalidFactory())) { invalidFactoryPool.setMaxIdle(1); invalidFactoryPool.setMaxTotal(1); @@ -1539,8 +1583,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { 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."); + assertEquals(0, invalidFactoryPool.getNumIdle(), "Idle count different than expected."); + assertEquals(0, invalidFactoryPool.getNumActive(), "Total count different than expected."); } } @@ -1572,18 +1616,22 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.setTestWhileIdle(true); // ClassNotFoundException - assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(Long.toString(System.currentTimeMillis())), + assertThrows(IllegalArgumentException.class, + () -> genericObjectPool.setEvictionPolicyClassName(Long.toString(System.currentTimeMillis())), "setEvictionPolicyClassName must throw an error if the class name is invalid."); // InstantiationException - assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.io.Serializable.class.getName()), + assertThrows(IllegalArgumentException.class, + () -> genericObjectPool.setEvictionPolicyClassName(java.io.Serializable.class.getName()), "setEvictionPolicyClassName must throw an error if the class name is invalid."); // IllegalAccessException - assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.util.Collections.class.getName()), + assertThrows(IllegalArgumentException.class, + () -> genericObjectPool.setEvictionPolicyClassName(java.util.Collections.class.getName()), "setEvictionPolicyClassName must throw an error if the class name is invalid."); - assertThrows(IllegalArgumentException.class, () -> genericObjectPool.setEvictionPolicyClassName(java.lang.String.class.getName()), + assertThrows(IllegalArgumentException.class, + () -> genericObjectPool.setEvictionPolicyClassName(java.lang.String.class.getName()), () -> "setEvictionPolicyClassName must throw an error if a class that does not implement EvictionPolicy is specified."); genericObjectPool.setEvictionPolicy(new TestEvictionPolicy<>()); @@ -1671,7 +1719,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { @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 + // when numTestsPerEvictionRun is negative, it represents a fraction of the idle + // objects to test genericObjectPool.setMaxIdle(6); genericObjectPool.setMaxTotal(6); genericObjectPool.setNumTestsPerEvictionRun(-2); @@ -1687,13 +1736,16 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } Waiter.sleepQuietly(100L); - assertTrue(genericObjectPool.getNumIdle() <= 6,"Should at most 6 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() <= 6, + "Should at most 6 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(100L); - assertTrue(genericObjectPool.getNumIdle() <= 3,"Should at most 3 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() <= 3, + "Should at most 3 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(100L); - assertTrue(genericObjectPool.getNumIdle() <= 2,"Should be at most 2 idle, found " + genericObjectPool.getNumIdle()); + assertTrue(genericObjectPool.getNumIdle() <= 2, + "Should be at most 2 idle, found " + genericObjectPool.getNumIdle()); Waiter.sleepQuietly(100L); - assertEquals(0,genericObjectPool.getNumIdle(),"Should be zero idle, found " + genericObjectPool.getNumIdle()); + assertEquals(0, genericObjectPool.getNumIdle(), "Should be zero idle, found " + genericObjectPool.getNumIdle()); } @Test @@ -1794,14 +1846,15 @@ public class TestGenericObjectPool extends TestBaseObjectPool { final String obj = genericObjectPool.borrowObject(); simpleFactory.setThrowExceptionOnPassivate(true); genericObjectPool.returnObject(obj); - assertEquals(0,genericObjectPool.getNumIdle()); + assertEquals(0, genericObjectPool.getNumIdle()); } @Test public void testFailingFactoryDoesNotBlockThreads() throws Exception { final CreateFailFactory factory = new CreateFailFactory(); - try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>(factory)) { + try (final GenericObjectPool<String, InterruptedException> createFailFactoryPool = new GenericObjectPool<>( + factory)) { createFailFactoryPool.setMaxTotal(1); @@ -1853,26 +1906,28 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.addObject(); // "0" genericObjectPool.addObject(); // "1" genericObjectPool.addObject(); // "2" - assertEquals( "0", genericObjectPool.borrowObject(),"Oldest"); - assertEquals( "1", genericObjectPool.borrowObject(),"Middle"); - assertEquals( "2", genericObjectPool.borrowObject(),"Youngest"); + 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"); + assertEquals("3", o, "new-3"); genericObjectPool.returnObject(o); - assertEquals( o, genericObjectPool.borrowObject(),"returned-3"); - assertEquals( "4", genericObjectPool.borrowObject(),"new-4"); + assertEquals(o, genericObjectPool.borrowObject(), "returned-3"); + assertEquals("4", genericObjectPool.borrowObject(), "new-4"); } @Test public void testGetFactoryType_DefaultPooledObjectFactory() { - try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(createDefaultPooledObjectFactory())) { + try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>( + createDefaultPooledObjectFactory())) { assertNotNull(pool.getFactoryType()); } } @Test public void testGetFactoryType_NullPooledObjectFactory() { - try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>(createNullPooledObjectFactory())) { + try (final GenericObjectPool<String, RuntimeException> pool = new GenericObjectPool<>( + createNullPooledObjectFactory())) { assertNotNull(pool.getFactoryType()); } } @@ -1918,7 +1973,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } /** - * Verify that threads waiting on a depleted pool get served when a checked out object is + * Verify that threads waiting on a depleted pool get served when a checked out + * object is * invalidated. * * JIRA: POOL-240 @@ -1939,7 +1995,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { // Launch another thread - will block, but fail in 500 ms final WaitingTestThread<TestException> thread2 = new WaitingTestThread<>(pool, 100); thread2.start(); - // Invalidate the object borrowed by this thread - should allow thread2 to create + // 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 @@ -1963,14 +2020,16 @@ public class TestGenericObjectPool extends TestBaseObjectPool { final GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>(); config.setJmxEnabled(false); - try (final GenericObjectPool<String, TestException> poolWithoutJmx = new GenericObjectPool<>(simpleFactory, config)) { + try (final GenericObjectPool<String, TestException> poolWithoutJmx = new GenericObjectPool<>(simpleFactory, + config)) { assertNull(poolWithoutJmx.getJmxName()); config.setJmxEnabled(true); poolWithoutJmx.jmxUnregister(); } config.setJmxNameBase(null); - try (final GenericObjectPool<String, TestException> poolWithDefaultJmxNameBase = new GenericObjectPool<>(simpleFactory, config)) { + try (final GenericObjectPool<String, TestException> poolWithDefaultJmxNameBase = new GenericObjectPool<>( + simpleFactory, config)) { assertNotNull(poolWithDefaultJmxNameBase.getJmxName()); } } @@ -1983,33 +2042,37 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.addObject(); // "0" genericObjectPool.addObject(); // "1" genericObjectPool.addObject(); // "2" - assertEquals( "2", genericObjectPool.borrowObject(),"Youngest"); - assertEquals( "1", genericObjectPool.borrowObject(),"Middle"); - assertEquals( "0", genericObjectPool.borrowObject(),"Oldest"); + assertEquals("2", genericObjectPool.borrowObject(), "Youngest"); + assertEquals("1", genericObjectPool.borrowObject(), "Middle"); + assertEquals("0", genericObjectPool.borrowObject(), "Oldest"); o = genericObjectPool.borrowObject(); - assertEquals( "3", o,"new-3"); + assertEquals("3", o, "new-3"); genericObjectPool.returnObject(o); - assertEquals( o, genericObjectPool.borrowObject(),"returned-3"); - assertEquals( "4", genericObjectPool.borrowObject(),"new-4"); + assertEquals(o, genericObjectPool.borrowObject(), "returned-3"); + assertEquals("4", genericObjectPool.borrowObject(), "new-4"); } /** * Simplest example of recovery from factory outage. - * A thread gets into parked wait on the deque when there is capacity to create, but - * creates are failing due to factory outage. Verify that the borrower is served + * A thread gets into parked wait on the deque when there is capacity to create, + * but + * creates are failing due to factory outage. Verify that the borrower is served * once the factory is back online. */ @Test - @Disabled @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) public void testLivenessOnTransientFactoryFailure() throws InterruptedException { final DisconnectingWaiterFactory<String> factory = new DisconnectingWaiterFactory<>( - DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_CREATE_ACTION, - DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_LIFECYCLE_ACTION, - obj -> false // all instances fail validation + DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_CREATE_ACTION, + DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_LIFECYCLE_ACTION, + obj -> false // all instances fail validation ); final AtomicBoolean failed = new AtomicBoolean(); - try (GenericObjectPool<Waiter, IllegalStateException> pool = new GenericObjectPool<>(factory)) { + final ResilientPooledObjectFactory<Waiter, IllegalStateException> resilientFactory = new ResilientPooledObjectFactory<>( + factory, 10, Duration.ofMillis(20), Duration.ofMinutes(10), Duration.ofMillis(20)); + try (GenericObjectPool<Waiter, IllegalStateException> pool = new GenericObjectPool<>(resilientFactory)) { + resilientFactory.setPool(pool); + resilientFactory.startMonitor(); pool.setMaxWait(Duration.ofMillis(100)); pool.setTestOnReturn(true); pool.setMaxTotal(1); @@ -2039,9 +2102,11 @@ public class TestGenericObjectPool extends TestBaseObjectPool { /** * 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 + * 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 @@ -2070,15 +2135,15 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.setMaxTotal(100); genericObjectPool.setMaxIdle(8); final String[] active = new String[100]; - for(int i=0;i<100;i++) { + 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++) { + 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()); + assertEquals(99 - i, genericObjectPool.getNumActive()); + assertEquals(i < 8 ? i + 1 : 8, genericObjectPool.getNumIdle()); } } @@ -2088,14 +2153,14 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.setMaxTotal(100); genericObjectPool.setMaxIdle(0); final String[] active = new String[100]; - for(int i=0;i<100;i++) { + 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++) { + 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(99 - i, genericObjectPool.getNumActive()); assertEquals(0, genericObjectPool.getNumIdle()); } } @@ -2123,7 +2188,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { // Start threads to borrow objects final TestThread[] threads = new TestThread[numThreads]; - for(int i=0;i<numThreads;i++) { + 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 @@ -2191,8 +2256,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { @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.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); @@ -2219,7 +2284,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { // Start threads to borrow objects final TestThread[] threads = new TestThread[numThreads]; - for(int i=0;i<numThreads;i++) { + 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 @@ -2256,10 +2321,10 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } for (int i = 0; i < numThreads; i++) { - while(!threads[i].complete()) { + while (!threads[i].complete()) { Waiter.sleepQuietly(500L); } - if(threads[i].failed()) { + if (threads[i].failed()) { fail("Thread " + i + " failed: " + threads[i].error.toString()); } } @@ -2277,7 +2342,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { * 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: + * 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! */ @@ -2302,30 +2368,29 @@ public class TestGenericObjectPool extends TestBaseObjectPool { int failed = 0; for (final WaitingTestThread<TestException> element : wtt) { element.join(); - if (element.thrown != null){ + if (element.thrown != null) { failed++; } } - if (DISPLAY_THREAD_DETAILS || wtt.length/2 != failed){ + if (DISPLAY_THREAD_DETAILS || wtt.length / 2 != failed) { System.out.println( "MaxWait: " + maxWait + - " HoldTime: " + holdTime + - " MaxTotal: " + threads + - " Threads: " + wtt.length + - " Failed: " + failed - ); + " HoldTime: " + holdTime + + " MaxTotal: " + threads + + " Threads: " + wtt.length + + " Failed: " + failed); for (final WaitingTestThread<TestException> 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 - ); + " 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"); + assertEquals(wtt.length / 2, failed, "Expected half the threads to fail"); } @Test @@ -2389,7 +2454,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { Waiter.sleepQuietly(150L); assertEquals(5, genericObjectPool.getNumIdle(), "Should be 5 idle, found " + genericObjectPool.getNumIdle()); - for(int i = 0 ; i < 5 ; i++) { + for (int i = 0; i < 5; i++) { genericObjectPool.returnObject(active[i]); } @@ -2412,7 +2477,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } /** - * Verifies that returning an object twice (without borrow in between) causes ISE + * 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 @@ -2440,7 +2506,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { // POOL-248 @Test public void testMultipleReturnOfSameObject() throws Exception { - try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(simpleFactory, new GenericObjectPoolConfig<>())) { + try (final GenericObjectPool<String, TestException> pool = new GenericObjectPool<>(simpleFactory, + new GenericObjectPoolConfig<>())) { assertEquals(0, pool.getNumActive()); assertEquals(0, pool.getNumIdle()); @@ -2489,7 +2556,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { genericObjectPool.setMaxTotal(-1); genericObjectPool.setBlockWhenExhausted(false); final String obj = genericObjectPool.borrowObject(); - assertEquals(getNthObject(0),obj); + assertEquals(getNthObject(0), obj); genericObjectPool.returnObject(obj); } @@ -2538,16 +2605,16 @@ public class TestGenericObjectPool extends TestBaseObjectPool { /** * Verify that when a factory returns a null object, pool methods throw NPE. + * * @throws InterruptedException */ @Test @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) public void testNPEOnFactoryNull() throws InterruptedException { final DisconnectingWaiterFactory<String> factory = new DisconnectingWaiterFactory<>( - () -> null, // Override default to always return null from makeObject - DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_LIFECYCLE_ACTION, - DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_VALIDATION_ACTION - ); + () -> null, // Override default to always return null from makeObject + DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_LIFECYCLE_ACTION, + DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_VALIDATION_ACTION); try (GenericObjectPool<Waiter, IllegalStateException> pool = new GenericObjectPool<>(factory)) { pool.setTestOnBorrow(true); pool.setMaxTotal(-1); @@ -2575,7 +2642,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { assertEquals(1, genericObjectPool.getNumIdle()); } - @Test/* maxWaitMillis x2 + padding */ + @Test /* maxWaitMillis x2 + padding */ @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS) public void testReturnBorrowObjectWithingMaxWaitMillis() throws Exception { final long maxWaitMillis = 500; @@ -2586,7 +2653,8 @@ public class TestGenericObjectPool extends TestBaseObjectPool { createSlowObjectFactoryPool.setMaxWait(Duration.ofMillis(maxWaitMillis)); // thread1 tries creating a slow object to make pool full. - final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createSlowObjectFactoryPool, 0); + final WaitingTestThread<InterruptedException> thread1 = new WaitingTestThread<>(createSlowObjectFactoryPool, + 0); thread1.start(); // Wait for thread1's reaching to create(). @@ -2608,7 +2676,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { * inserted just before the final call to isLifo() in the returnObject() * method. */ - //@Test + // @Test @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) public void testReturnObject() throws Exception { @@ -2636,7 +2704,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) public void testSetConfig() throws Exception { final GenericObjectPoolConfig<String> expected = new GenericObjectPoolConfig<>(); - assertConfiguration(expected,genericObjectPool); + assertConfiguration(expected, genericObjectPool); expected.setMaxTotal(2); expected.setMaxIdle(3); expected.setMaxWait(Duration.ofMillis(5)); @@ -2649,7 +2717,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { expected.setDurationBetweenEvictionRuns(Duration.ofMillis(11L)); expected.setBlockWhenExhausted(false); genericObjectPool.setConfig(expected); - assertConfiguration(expected,genericObjectPool); + assertConfiguration(expected, genericObjectPool); } @Test @@ -2733,11 +2801,11 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } { genericObjectPool.setDurationBetweenEvictionRuns(Duration.ofMillis(11235)); - assertEquals(11235L,genericObjectPool.getDurationBetweenEvictionRuns().toMillis()); + assertEquals(11235L, genericObjectPool.getDurationBetweenEvictionRuns().toMillis()); } { genericObjectPool.setSoftMinEvictableIdleDuration(Duration.ofMillis(12135)); - assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleDuration().toMillis()); + assertEquals(12135L, genericObjectPool.getSoftMinEvictableIdleDuration().toMillis()); } { genericObjectPool.setBlockWhenExhausted(true); @@ -2769,7 +2837,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { } // note that it stays populated - assertEquals(6,genericObjectPool.getNumIdle(),"Should have 6 idle"); + assertEquals(6, genericObjectPool.getNumIdle(), "Should have 6 idle"); // start the evictor genericObjectPool.setDurationBetweenEvictionRuns(Duration.ofMillis(50)); @@ -2778,7 +2846,7 @@ public class TestGenericObjectPool extends TestBaseObjectPool { Waiter.sleepQuietly(200L); // assert that the evictor has cleared out the pool - assertEquals(0,genericObjectPool.getNumIdle(),"Should have 0 idle"); + assertEquals(0, genericObjectPool.getNumIdle(), "Should have 0 idle"); // stop the evictor genericObjectPool.startEvictor(Duration.ZERO); @@ -2867,10 +2935,11 @@ public class TestGenericObjectPool extends TestBaseObjectPool { // 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 + * Verify that threads waiting on a depleted pool get served when a returning + * object fails * validation. * * JIRA: POOL-240 diff --git a/src/test/java/org/apache/commons/pool3/impl/TestResilientPooledObjectFactory.java b/src/test/java/org/apache/commons/pool3/impl/TestResilientPooledObjectFactory.java new file mode 100644 index 00000000..29b03665 --- /dev/null +++ b/src/test/java/org/apache/commons/pool3/impl/TestResilientPooledObjectFactory.java @@ -0,0 +1,339 @@ +/* + * 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.pool3.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Duration; +import java.util.UUID; + +import org.apache.commons.pool3.PooledObject; +import org.apache.commons.pool3.PooledObjectFactory; +import org.junit.jupiter.api.Test; + +public class TestResilientPooledObjectFactory { + @Test + public void testTransientFailure() throws Exception { + FailingFactory ff = new FailingFactory(); + // Make the factory fail with exception immmmediately on make + ff.setHang(false); + ff.setSilentFail(false); + ResilientPooledObjectFactory<String, Exception> rf = new ResilientPooledObjectFactory<String, Exception>(ff, + 5, Duration.ofMillis(100), Duration.ofMinutes(10), Duration.ofMillis(100)); + // Create a pool with a max size of 2, using the resilient factory + GenericObjectPool<String, Exception> pool = new GenericObjectPool<>(rf); + pool.setMaxTotal(2); + pool.setBlockWhenExhausted(true); + pool.setTestOnReturn(true); + // Tell the factory what pool it is attached to (a little awkward) + rf.setPool(pool); + rf.startMonitor(Duration.ofMillis(20)); // 20ms monitor interval + // Base factory is up + assertTrue(rf.isUp()); + // Check out a couple of objects + String s1 = pool.borrowObject(); + String s2 = pool.borrowObject(); + // Start a borrower that will wait + new Thread() { + @Override + public void run() { + try { + pool.borrowObject(); + } catch (Exception e) { + } + } + }.start(); + // Wait for the borrower to join wait queue + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + + // Crash the base factory + ff.crash(); + // Resilient factory does not know the base factory is down until a make is + // attempted + assertTrue(rf.isUp()); + assertEquals(1, pool.getNumWaiters()); + pool.returnObject(s1); + pool.returnObject(s2); + assertEquals(0, pool.getNumIdle()); + assertEquals(1, pool.getNumWaiters()); + // Wait for the monitor to pick up the failed create which should happen on + // validation destroy + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + assertFalse(rf.isUp()); + // Adder should be running, but failing + assertTrue(rf.isAdderRunning()); + + // Pool should have one take waiter + assertEquals(1, pool.getNumWaiters()); + + // Restart the factory + ff.recover(); + // Wait for the adder to succeed + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + // Pool should have no waiters + assertTrue(pool.getNumWaiters() == 0); + // Add 5 objects to clear the rf log + // First have to expand the pool + pool.setMaxTotal(10); + for (int i = 0; i < 5; i++) { + pool.addObject(); + } + // Wait for monitor to run + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + // rf should be up now + assertTrue(rf.isUp()); + + // Adder should be stopped + assertFalse(rf.isAdderRunning()); + + pool.close(); + rf.stopMonitor(); + } + + @Test + public void testNulls() throws Exception { + FailingFactory ff = new FailingFactory(); + // Make the factory fail with exception immmmediately on make + ff.setSilentFail(true); + ResilientPooledObjectFactory<String, Exception> rf = new ResilientPooledObjectFactory<String, Exception>(ff, + 5, Duration.ofMillis(50), Duration.ofMinutes(10), Duration.ofMillis(50)); + // Create a pool with a max size of 2, using the resilient factory + GenericObjectPool<String, Exception> pool = new GenericObjectPool<>(rf); + pool.setMaxTotal(2); + pool.setBlockWhenExhausted(true); + pool.setTestOnReturn(true); + // Tell the factory what pool it is attached to (a little awkward) + rf.setPool(pool); + rf.startMonitor(Duration.ofMillis(20)); // 20ms monitor interval + + // Exhaust the pool + final String s1 = pool.borrowObject(); + final String s2 = pool.borrowObject(); + ff.crash(); + // Start two borrowers that will block waiting + new Thread() { + @Override + public void run() { + try { + final String s = pool.borrowObject(); + pool.returnObject(s); + } catch (Exception e) { + } + } + }.start(); + new Thread() { + @Override + public void run() { + try { + final String s = pool.borrowObject(); + pool.returnObject(s); + } catch (Exception e) { + } + } + }.start(); + // Return borrowed objects - validation will fail + // Wait for the borrowers to get in the queue + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + pool.returnObject(s1); + pool.returnObject(s2); + assertEquals(0, pool.getNumIdle()); + assertTrue(pool.getNumWaiters() > 0); + // Wait for the monitor to pick up the failed create which should happen on + // validation destroy + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + assertFalse(rf.isUp()); + // Restart the factory + ff.recover(); + // Wait for the adder to succeed + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + // Pool should have no waiters + assertEquals(0, pool.getNumWaiters()); + pool.close(); + // Wait for monitor to run + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + // Monitor and adder should be stopped by pool close + assertFalse(rf.isAdderRunning()); + assertFalse(rf.isMonitorRunning()); + } + + @Test + public void testAdderStartStop() throws Exception { + FailingFactory ff = new FailingFactory(); + // Make the factory fail with exception immmmediately on make + ff.setSilentFail(true); + ResilientPooledObjectFactory<String, Exception> rf = new ResilientPooledObjectFactory<String, Exception>(ff, + 5, Duration.ofMillis(200), Duration.ofMinutes(10), Duration.ofMillis(20)); + // Create a pool with a max size of 2, using the resilient factory + GenericObjectPool<String, Exception> pool = new GenericObjectPool<>(rf); + pool.setMaxTotal(2); + pool.setBlockWhenExhausted(true); + pool.setTestOnReturn(true); + rf.setPool(pool); + rf.startMonitor(); + // Exhasut the pool + final String s1 = pool.borrowObject(); + final String s2 = pool.borrowObject(); + // Start a borrower that will block waiting + new Thread() { + @Override + public void run() { + try { + final String s = pool.borrowObject(); + } catch (Exception e) { + } finally { + System.out.println("Borrower done"); + } + } + }.start(); + // Wait for the borrower to get in the queue + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + // Crash the base factory + ff.crash(); + // Return object will create capacity in the pool + try { + pool.returnObject(s1); + } catch (Exception e) { + } + // Wait for the adder to run + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + // Adder should be running + assertTrue(rf.isAdderRunning()); + // Restart the factory + ff.recover(); + // Wait for the adder to succeed + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + // Pool should have no waiters + assertEquals(0, pool.getNumWaiters()); + // Adder should still be running because there is a failure in the log + assertTrue(rf.isAdderRunning()); + // Expand the pool - don't try this at home + pool.setMaxTotal(10); + // Run enough adds to homogenize the log + for (int i = 0; i < 6; i++) { + pool.addObject(); + } + // Wait for the monitor to run + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + assertTrue(rf.isUp()); + // Adder should be stopped + assertFalse(rf.isAdderRunning()); + } + + /** + * Factory that suffers outages and fails in configurable ways when it is down. + */ + class FailingFactory implements PooledObjectFactory<String, Exception> { + /** Wheter or not the factory is up */ + private boolean up = true; + + /** Whether or not to fail silently */ + private boolean silentFail = true; + + /** Whether or not to hang */ + private boolean hang = false; + + public void crash() { + this.up = false; + } + + public void recover() { + this.up = true; + } + + public void setSilentFail(boolean silentFail) { + this.silentFail = silentFail; + } + + public void setHang(boolean hang) { + this.hang = hang; + } + + @Override + public PooledObject<String> makeObject() throws Exception { + if (up) { + return new DefaultPooledObject<String>(UUID.randomUUID().toString()); + } + if (!silentFail) { + throw new Exception("makeObject failed"); + } + if (hang) { + while (!up) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + } + return null; + } + + @Override + public void destroyObject(PooledObject<String> p) throws Exception { + } + + @Override + public boolean validateObject(PooledObject<String> p) { + return up; + } + + @Override + public void activateObject(PooledObject<String> p) throws Exception { + } + + @Override + public void passivateObject(PooledObject<String> p) throws Exception { + } + } +} \ No newline at end of file