View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.pool2.impl;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotEquals;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNotSame;
24  import static org.junit.jupiter.api.Assertions.assertSame;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  import static org.junit.jupiter.api.Assertions.fail;
28  
29  import java.lang.management.ManagementFactory;
30  import java.time.Duration;
31  import java.time.Instant;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.HashSet;
35  import java.util.List;
36  import java.util.NoSuchElementException;
37  import java.util.Objects;
38  import java.util.Random;
39  import java.util.Set;
40  import java.util.Timer;
41  import java.util.TimerTask;
42  import java.util.concurrent.ConcurrentHashMap;
43  import java.util.concurrent.ExecutionException;
44  import java.util.concurrent.ExecutorService;
45  import java.util.concurrent.Executors;
46  import java.util.concurrent.Future;
47  import java.util.concurrent.Semaphore;
48  import java.util.concurrent.ThreadFactory;
49  import java.util.concurrent.TimeUnit;
50  import java.util.concurrent.atomic.AtomicInteger;
51  
52  import javax.management.MBeanServer;
53  import javax.management.ObjectName;
54  
55  import org.apache.commons.lang3.exception.ExceptionUtils;
56  import org.apache.commons.lang3.function.Suppliers;
57  import org.apache.commons.pool2.AbstractTestKeyedObjectPool;
58  import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
59  import org.apache.commons.pool2.DestroyMode;
60  import org.apache.commons.pool2.KeyedObjectPool;
61  import org.apache.commons.pool2.KeyedPooledObjectFactory;
62  import org.apache.commons.pool2.PooledObject;
63  import org.apache.commons.pool2.VisitTracker;
64  import org.apache.commons.pool2.VisitTrackerFactory;
65  import org.apache.commons.pool2.Waiter;
66  import org.apache.commons.pool2.WaiterFactory;
67  import org.junit.jupiter.api.AfterEach;
68  import org.junit.jupiter.api.BeforeEach;
69  import org.junit.jupiter.api.Test;
70  import org.junit.jupiter.api.Timeout;
71  import org.junit.jupiter.params.ParameterizedTest;
72  import org.junit.jupiter.params.provider.EnumSource;
73  
74  /**
75   */
76  public class TestGenericKeyedObjectPool extends AbstractTestKeyedObjectPool {
77      private static final class DaemonThreadFactory implements ThreadFactory {
78          @Override
79          public Thread newThread(final Runnable r) {
80              final Thread t = new Thread(r);
81              t.setDaemon(true);
82              return t;
83          }
84      }
85  
86      private static final class DummyFactory extends BaseKeyedPooledObjectFactory<Object, Object> {
87          @Override
88          public Object create(final Object key) {
89              return null;
90          }
91  
92          @Override
93          public PooledObject<Object> wrap(final Object value) {
94              return new DefaultPooledObject<>(value);
95          }
96      }
97  
98      /**
99       * Factory that creates HashSets. Note that this means 0) All instances are initially equal (not discernible by equals) 1) Instances are mutable and
100      * mutation can cause change in identity / hash code.
101      */
102     private static final class HashSetFactory extends BaseKeyedPooledObjectFactory<String, HashSet<String>> {
103         @Override
104         public HashSet<String> create(final String key) {
105             return new HashSet<>();
106         }
107 
108         @Override
109         public PooledObject<HashSet<String>> wrap(final HashSet<String> value) {
110             return new DefaultPooledObject<>(value);
111         }
112     }
113 
114     /**
115      * Attempts to invalidate an object, swallowing IllegalStateException.
116      */
117     private static class InvalidateThread implements Runnable {
118         private final String obj;
119         private final KeyedObjectPool<String, String> pool;
120         private final String key;
121         private boolean done;
122 
123         private InvalidateThread(final KeyedObjectPool<String, String> pool, final String key, final String obj) {
124             this.obj = obj;
125             this.pool = pool;
126             this.key = key;
127         }
128 
129         private boolean complete() {
130             return done;
131         }
132 
133         @Override
134         public void run() {
135             try {
136                 pool.invalidateObject(key, obj);
137             } catch (final IllegalStateException ex) {
138                 // Ignore
139             } catch (final Exception ex) {
140                 fail("Unexpected exception " + ex.toString());
141             } finally {
142                 done = true;
143             }
144         }
145     }
146 
147     private static final class ObjectFactory extends BaseKeyedPooledObjectFactory<Integer, Object> {
148         @Override
149         public Object create(final Integer key) {
150             return new Object();
151         }
152 
153         @Override
154         public PooledObject<Object> wrap(final Object value) {
155             return new DefaultPooledObject<>(value);
156         }
157     }
158 
159     public static class SimpleFactory<K> implements KeyedPooledObjectFactory<K, String> {
160         volatile int counter;
161         final boolean valid;
162         int activeCount;
163         int validateCounter;
164         boolean evenValid = true;
165         boolean oddValid = true;
166         boolean enableValidation;
167         long destroyLatency;
168         long makeLatency;
169         long validateLatency;
170         volatile int maxTotalPerKey = Integer.MAX_VALUE;
171         boolean exceptionOnPassivate;
172         boolean exceptionOnActivate;
173         boolean exceptionOnDestroy;
174         boolean exceptionOnValidate;
175         boolean exceptionOnCreate;
176 
177         public SimpleFactory() {
178             this(true);
179         }
180 
181         public SimpleFactory(final boolean valid) {
182             this.valid = valid;
183         }
184 
185         @Override
186         public void activateObject(final K key, final PooledObject<String> obj) throws Exception {
187             if (exceptionOnActivate && !(validateCounter++ % 2 == 0 ? evenValid : oddValid)) {
188                 throw new Exception();
189             }
190         }
191 
192         @Override
193         public void destroyObject(final K key, final PooledObject<String> obj) throws Exception {
194             doWait(destroyLatency);
195             synchronized (this) {
196                 activeCount--;
197             }
198             if (exceptionOnDestroy) {
199                 throw new Exception();
200             }
201         }
202 
203         private void doWait(final long latency) {
204             Waiter.sleepQuietly(latency);
205         }
206 
207         @Override
208         public PooledObject<String> makeObject(final K key) throws Exception {
209             if (exceptionOnCreate) {
210                 throw new Exception();
211             }
212             doWait(makeLatency);
213             String out = null;
214             synchronized (this) {
215                 activeCount++;
216                 if (activeCount > maxTotalPerKey) {
217                     throw new IllegalStateException("Too many active instances: " + activeCount);
218                 }
219                 out = String.valueOf(key) + String.valueOf(counter++);
220             }
221             return new DefaultPooledObject<>(out);
222         }
223 
224         @Override
225         public void passivateObject(final K key, final PooledObject<String> obj) throws Exception {
226             if (exceptionOnPassivate) {
227                 throw new Exception();
228             }
229         }
230 
231         public void setDestroyLatency(final long destroyLatency) {
232             this.destroyLatency = destroyLatency;
233         }
234 
235         void setEvenValid(final boolean valid) {
236             evenValid = valid;
237         }
238 
239         public void setMakeLatency(final long makeLatency) {
240             this.makeLatency = makeLatency;
241         }
242 
243         public void setMaxTotalPerKey(final int maxTotalPerKey) {
244             this.maxTotalPerKey = maxTotalPerKey;
245         }
246 
247         public void setThrowExceptionOnActivate(final boolean b) {
248             exceptionOnActivate = b;
249         }
250 
251         public void setThrowExceptionOnDestroy(final boolean b) {
252             exceptionOnDestroy = b;
253         }
254 
255         public void setThrowExceptionOnPassivate(final boolean b) {
256             exceptionOnPassivate = b;
257         }
258 
259         public void setThrowExceptionOnValidate(final boolean b) {
260             exceptionOnValidate = b;
261         }
262 
263         void setValid(final boolean valid) {
264             evenValid = valid;
265             oddValid = valid;
266         }
267 
268         public void setValidateLatency(final long validateLatency) {
269             this.validateLatency = validateLatency;
270         }
271 
272         public void setValidationEnabled(final boolean b) {
273             enableValidation = b;
274         }
275 
276         @Override
277         public boolean validateObject(final K key, final PooledObject<String> obj) {
278             doWait(validateLatency);
279             if (exceptionOnValidate) {
280                 throw new RuntimeException("validation failed");
281             }
282             if (enableValidation) {
283                 return validateCounter++ % 2 == 0 ? evenValid : oddValid;
284             }
285             return valid;
286         }
287     }
288 
289     private static final class SimplePerKeyFactory extends BaseKeyedPooledObjectFactory<Object, Object> {
290         final ConcurrentHashMap<Object, AtomicInteger> map = new ConcurrentHashMap<>();
291 
292         @Override
293         public Object create(final Object key) {
294             final int counter = map.computeIfAbsent(key, k -> new AtomicInteger(-1)).incrementAndGet();
295             return String.valueOf(key) + String.valueOf(counter);
296         }
297 
298         @Override
299         public PooledObject<Object> wrap(final Object value) {
300             return new DefaultPooledObject<>(value);
301         }
302     }
303 
304     /**
305      * Very simple test thread that just tries to borrow an object from the provided pool with the specified key and returns it.
306      *
307      * @param <T> Type of element in the pool.
308      */
309     private static class SimpleTestThread<T> implements Runnable {
310         private final KeyedObjectPool<String, T> pool;
311         private final String key;
312 
313         SimpleTestThread(final KeyedObjectPool<String, T> pool, final String key) {
314             this.pool = pool;
315             this.key = key;
316         }
317 
318         @Override
319         public void run() {
320                 try {
321                     pool.returnObject(key, pool.borrowObject(key));
322                 } catch (final Exception e) {
323                     // Ignore
324                 }
325         }
326     }
327 
328     /**
329      * DefaultEvictionPolicy modified to add latency.
330      *
331      * @param <T> the type of objects in the pool.
332      */
333     private static final class SlowEvictionPolicy<T> extends DefaultEvictionPolicy<T> {
334         private final long delay;
335 
336         /**
337          * Constructs SlowEvictionPolicy with the given delay in ms
338          *
339          * @param delay number of ms of latency to inject in evict
340          */
341         SlowEvictionPolicy(final long delay) {
342             this.delay = delay;
343         }
344 
345         @Override
346         public boolean evict(final EvictionConfig config, final PooledObject<T> underTest, final int idleCount) {
347             Waiter.sleepQuietly(delay);
348             return super.evict(config, underTest, idleCount);
349         }
350     }
351 
352     private static class TestThread<T> implements Runnable {
353         private final Random random = new Random();
354         /** GKOP to hit */
355         private final KeyedObjectPool<String, T> pool;
356         /** Number of borrow/return iterations */
357         private final int iter;
358         /** Delay before borrow */
359         private final int startDelay;
360         /** Delay before return */
361         private final int holdTime;
362         /** Whether or not delays are random (with max = configured values) */
363         private final boolean randomDelay;
364         /** Expected object */
365         private final T expectedObject;
366         /** Key used in borrow / return sequence - null means random */
367         private final String key;
368         private volatile boolean complete;
369         private volatile boolean failed;
370         private volatile Exception exception;
371 
372         private TestThread(final KeyedObjectPool<String, T> pool) {
373             this(pool, 100, 50, 50, true, null, null);
374         }
375 
376         private TestThread(final KeyedObjectPool<String, T> pool, final int iter) {
377             this(pool, iter, 50, 50, true, null, null);
378         }
379 
380         private TestThread(final KeyedObjectPool<String, T> pool, final int iter, final int delay) {
381             this(pool, iter, delay, delay, true, null, null);
382         }
383 
384         private TestThread(final KeyedObjectPool<String, T> pool, final int iter, final int startDelay, final int holdTime, final boolean randomDelay,
385                 final T expectedObject, final String key) {
386             this.pool = pool;
387             this.iter = iter;
388             this.startDelay = startDelay;
389             this.holdTime = holdTime;
390             this.randomDelay = randomDelay;
391             this.expectedObject = expectedObject;
392             this.key = key;
393         }
394 
395         private boolean complete() {
396             return complete;
397         }
398 
399         private boolean failed() {
400             return failed;
401         }
402 
403         @Override
404         public void run() {
405             for (int i = 0; i < iter; i++) {
406                 final String actualKey = key == null ? String.valueOf(random.nextInt(3)) : key;
407                 Waiter.sleepQuietly(randomDelay ? random.nextInt(startDelay) : startDelay);
408                 T obj = null;
409                 try {
410                     obj = pool.borrowObject(actualKey);
411                 } catch (final Exception e) {
412                     exception = e;
413                     failed = true;
414                     complete = true;
415                     break;
416                 }
417                 if (expectedObject != null && !expectedObject.equals(obj)) {
418                     exception = new Exception("Expected: " + expectedObject + " found: " + obj);
419                     failed = true;
420                     complete = true;
421                     break;
422                 }
423                 Waiter.sleepQuietly(randomDelay ? random.nextInt(holdTime) : holdTime);
424                 try {
425                     pool.returnObject(actualKey, obj);
426                 } catch (final Exception e) {
427                     exception = e;
428                     failed = true;
429                     complete = true;
430                     break;
431                 }
432             }
433             complete = true;
434         }
435     }
436 
437     /*
438      * Very simple test thread that just tries to borrow an object from the provided pool with the specified key and returns it after a wait
439      */
440     private static class WaitingTestThread extends Thread {
441         private final KeyedObjectPool<String, String> pool;
442         private final String key;
443         private final long pause;
444         private Throwable thrown;
445         private long preBorrowMillis; // just before borrow
446         private long postBorrowMillis; // borrow returned
447         private long postReturnMillis; // after object was returned
448         private long endedMillis;
449         private String objectId;
450 
451         private WaitingTestThread(final KeyedObjectPool<String, String> pool, final String key, final long pause) {
452             this.pool = pool;
453             this.key = key;
454             this.pause = pause;
455             this.thrown = null;
456         }
457 
458         @Override
459         public void run() {
460             try {
461                 preBorrowMillis = System.currentTimeMillis();
462                 final String obj = pool.borrowObject(key);
463                 objectId = obj;
464                 postBorrowMillis = System.currentTimeMillis();
465                 Thread.sleep(pause);
466                 pool.returnObject(key, obj);
467                 postReturnMillis = System.currentTimeMillis();
468             } catch (final Exception e) {
469                 thrown = e;
470             } finally {
471                 endedMillis = System.currentTimeMillis();
472             }
473         }
474     }
475 
476     private static final Integer KEY_ZERO = Integer.valueOf(0);
477     private static final Integer KEY_ONE = Integer.valueOf(1);
478     private static final Integer KEY_TWO = Integer.valueOf(2);
479     private static final boolean DISPLAY_THREAD_DETAILS = Boolean.getBoolean("TestGenericKeyedObjectPool.display.thread.details");
480     // To pass this to a Maven test, use:
481     // mvn test -DargLine="-DTestGenericKeyedObjectPool.display.thread.details=true"
482     // @see https://issues.apache.org/jira/browse/SUREFIRE-121
483     /** SetUp(): {@code new GenericKeyedObjectPool<String,String>(factory)} */
484     private GenericKeyedObjectPool<String, String> gkoPool;
485     /** SetUp(): {@code new SimpleFactory<String>()} */
486     private SimpleFactory<String> simpleFactory;
487 
488     private void checkEvictionOrder(final boolean lifo) throws Exception {
489         final SimpleFactory<Integer> intFactory = new SimpleFactory<>();
490         try (GenericKeyedObjectPool<Integer, String> intPool = new GenericKeyedObjectPool<>(intFactory)) {
491             intPool.setNumTestsPerEvictionRun(2);
492             intPool.setMinEvictableIdleTime(Duration.ofMillis(100));
493             intPool.setLifo(lifo);
494             for (int i = 0; i < 3; i++) {
495                 final Integer key = Integer.valueOf(i);
496                 for (int j = 0; j < 5; j++) {
497                     intPool.addObject(key);
498                 }
499             }
500             // Make all evictable
501             Thread.sleep(200);
502             /*
503              * Initial state (Key, Object) pairs in order of age:
504              *
505              * (0,0), (0,1), (0,2), (0,3), (0,4) (1,5), (1,6), (1,7), (1,8), (1,9) (2,10), (2,11), (2,12), (2,13), (2,14)
506              */
507             intPool.evict(); // Kill (0,0),(0,1)
508             assertEquals(3, intPool.getNumIdle(KEY_ZERO));
509             final String objZeroA = intPool.borrowObject(KEY_ZERO);
510             assertTrue(lifo ? objZeroA.equals("04") : objZeroA.equals("02"));
511             assertEquals(2, intPool.getNumIdle(KEY_ZERO));
512             final String objZeroB = intPool.borrowObject(KEY_ZERO);
513             assertEquals("03", objZeroB);
514             assertEquals(1, intPool.getNumIdle(KEY_ZERO));
515             intPool.evict(); // Kill remaining 0 survivor and (1,5)
516             assertEquals(0, intPool.getNumIdle(KEY_ZERO));
517             assertEquals(4, intPool.getNumIdle(KEY_ONE));
518             final String objOneA = intPool.borrowObject(KEY_ONE);
519             assertTrue(lifo ? objOneA.equals("19") : objOneA.equals("16"));
520             assertEquals(3, intPool.getNumIdle(KEY_ONE));
521             final String objOneB = intPool.borrowObject(KEY_ONE);
522             assertTrue(lifo ? objOneB.equals("18") : objOneB.equals("17"));
523             assertEquals(2, intPool.getNumIdle(KEY_ONE));
524             intPool.evict(); // Kill remaining 1 survivors
525             assertEquals(0, intPool.getNumIdle(KEY_ONE));
526             intPool.evict(); // Kill (2,10), (2,11)
527             assertEquals(3, intPool.getNumIdle(KEY_TWO));
528             final String objTwoA = intPool.borrowObject(KEY_TWO);
529             assertTrue(lifo ? objTwoA.equals("214") : objTwoA.equals("212"));
530             assertEquals(2, intPool.getNumIdle(KEY_TWO));
531             intPool.evict(); // All dead now
532             assertEquals(0, intPool.getNumIdle(KEY_TWO));
533             intPool.evict(); // Should do nothing - make sure no exception
534             // Currently 2 zero, 2 one and 1 two active. Return them
535             intPool.returnObject(KEY_ZERO, objZeroA);
536             intPool.returnObject(KEY_ZERO, objZeroB);
537             intPool.returnObject(KEY_ONE, objOneA);
538             intPool.returnObject(KEY_ONE, objOneB);
539             intPool.returnObject(KEY_TWO, objTwoA);
540             // Remove all idle objects
541             intPool.clear();
542             // Reload
543             intPool.setMinEvictableIdleTime(Duration.ofMillis(500));
544             intFactory.counter = 0; // Reset counter
545             for (int i = 0; i < 3; i++) {
546                 final Integer key = Integer.valueOf(i);
547                 for (int j = 0; j < 5; j++) {
548                     intPool.addObject(key);
549                 }
550                 Thread.sleep(200);
551             }
552             // 0's are evictable, others not
553             intPool.evict(); // Kill (0,0),(0,1)
554             assertEquals(3, intPool.getNumIdle(KEY_ZERO));
555             intPool.evict(); // Kill (0,2),(0,3)
556             assertEquals(1, intPool.getNumIdle(KEY_ZERO));
557             intPool.evict(); // Kill (0,4), leave (1,5)
558             assertEquals(0, intPool.getNumIdle(KEY_ZERO));
559             assertEquals(5, intPool.getNumIdle(KEY_ONE));
560             assertEquals(5, intPool.getNumIdle(KEY_TWO));
561             intPool.evict(); // (1,6), (1,7)
562             assertEquals(5, intPool.getNumIdle(KEY_ONE));
563             assertEquals(5, intPool.getNumIdle(KEY_TWO));
564             intPool.evict(); // (1,8), (1,9)
565             assertEquals(5, intPool.getNumIdle(KEY_ONE));
566             assertEquals(5, intPool.getNumIdle(KEY_TWO));
567             intPool.evict(); // (2,10), (2,11)
568             assertEquals(5, intPool.getNumIdle(KEY_ONE));
569             assertEquals(5, intPool.getNumIdle(KEY_TWO));
570             intPool.evict(); // (2,12), (2,13)
571             assertEquals(5, intPool.getNumIdle(KEY_ONE));
572             assertEquals(5, intPool.getNumIdle(KEY_TWO));
573             intPool.evict(); // (2,14), (1,5)
574             assertEquals(5, intPool.getNumIdle(KEY_ONE));
575             assertEquals(5, intPool.getNumIdle(KEY_TWO));
576             Thread.sleep(200); // Ones now timed out
577             intPool.evict(); // kill (1,6), (1,7) - (1,5) missed
578             assertEquals(3, intPool.getNumIdle(KEY_ONE));
579             assertEquals(5, intPool.getNumIdle(KEY_TWO));
580             final String obj = intPool.borrowObject(KEY_ONE);
581             if (lifo) {
582                 assertEquals("19", obj);
583             } else {
584                 assertEquals("15", obj);
585             }
586         }
587     }
588 
589     private void checkEvictorVisiting(final boolean lifo) throws Exception {
590         VisitTrackerFactory<Integer> trackerFactory = new VisitTrackerFactory<>();
591         try (GenericKeyedObjectPool<Integer, VisitTracker<Integer>> intPool = new GenericKeyedObjectPool<>(trackerFactory)) {
592             intPool.setNumTestsPerEvictionRun(2);
593             intPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
594             intPool.setTestWhileIdle(true);
595             intPool.setLifo(lifo);
596             intPool.setTestOnReturn(false);
597             intPool.setTestOnBorrow(false);
598             for (int i = 0; i < 3; i++) {
599                 trackerFactory.resetId();
600                 final Integer key = Integer.valueOf(i);
601                 for (int j = 0; j < 8; j++) {
602                     intPool.addObject(key);
603                 }
604             }
605             intPool.evict(); // Visit oldest 2 - 00 and 01
606             VisitTracker<Integer> obj = intPool.borrowObject(KEY_ZERO);
607             intPool.returnObject(KEY_ZERO, obj);
608             obj = intPool.borrowObject(KEY_ZERO);
609             intPool.returnObject(KEY_ZERO, obj);
610             // borrow, return, borrow, return
611             // FIFO will move 0 and 1 to end - 2,3,4,5,6,7,0,1
612             // LIFO, 7 out, then in, then out, then in - 7,6,5,4,3,2,1,0
613             intPool.evict(); // Should visit 02 and 03 in either case
614             for (int i = 0; i < 8; i++) {
615                 final VisitTracker<Integer> tracker = intPool.borrowObject(KEY_ZERO);
616                 if (tracker.getId() >= 4) {
617                     assertEquals(0, tracker.getValidateCount(), "Unexpected instance visited " + tracker.getId());
618                 } else {
619                     assertEquals(1, tracker.getValidateCount(), "Instance " + tracker.getId() + " visited wrong number of times.");
620                 }
621             }
622             // 0's are all out
623             intPool.setNumTestsPerEvictionRun(3);
624             intPool.evict(); // 10, 11, 12
625             intPool.evict(); // 13, 14, 15
626             obj = intPool.borrowObject(KEY_ONE);
627             intPool.returnObject(KEY_ONE, obj);
628             obj = intPool.borrowObject(KEY_ONE);
629             intPool.returnObject(KEY_ONE, obj);
630             obj = intPool.borrowObject(KEY_ONE);
631             intPool.returnObject(KEY_ONE, obj);
632             // borrow, return, borrow, return
633             // FIFO 3,4,5,^,6,7,0,1,2
634             // LIFO 7,6,^,5,4,3,2,1,0
635             // In either case, pointer should be at 6
636             intPool.evict();
637             // LIFO - 16, 17, 20
638             // FIFO - 16, 17, 10
639             intPool.evict();
640             // LIFO - 21, 22, 23
641             // FIFO - 11, 12, 20
642             intPool.evict();
643             // LIFO - 24, 25, 26
644             // FIFO - 21, 22, 23
645             intPool.evict();
646             // LIFO - 27, 10, 11
647             // FIFO - 24, 25, 26
648             for (int i = 0; i < 8; i++) {
649                 final VisitTracker<Integer> tracker = intPool.borrowObject(KEY_ONE);
650                 if (lifo && tracker.getId() > 1 || !lifo && tracker.getId() > 2) {
651                     assertEquals(1, tracker.getValidateCount(), "Instance " + tracker.getId() + " visited wrong number of times.");
652                 } else {
653                     assertEquals(2, tracker.getValidateCount(), "Instance " + tracker.getId() + " visited wrong number of times.");
654                 }
655             }
656         }
657         // Randomly generate some pools with random numTests
658         // and make sure evictor cycles through elements appropriately
659         final int[] smallPrimes = { 2, 3, 5, 7 };
660         final Random random = new Random();
661         random.setSeed(System.currentTimeMillis());
662         for (int i = 0; i < smallPrimes.length; i++) {
663             for (int j = 0; j < 5; j++) { // Try the tests a few times
664                 // Can't use clear as some objects are still active so create
665                 // a new pool
666                 trackerFactory = new VisitTrackerFactory<>();
667                 try (GenericKeyedObjectPool<Integer, VisitTracker<Integer>> intPool = new GenericKeyedObjectPool<>(trackerFactory)) {
668                     intPool.setMaxIdlePerKey(-1);
669                     intPool.setMaxTotalPerKey(-1);
670                     intPool.setNumTestsPerEvictionRun(smallPrimes[i]);
671                     intPool.setMinEvictableIdleTime(Duration.ofMillis(-1));
672                     intPool.setTestWhileIdle(true);
673                     intPool.setLifo(lifo);
674                     intPool.setTestOnReturn(false);
675                     intPool.setTestOnBorrow(false);
676                     final int zeroLength = 10 + random.nextInt(20);
677                     for (int k = 0; k < zeroLength; k++) {
678                         intPool.addObject(KEY_ZERO);
679                     }
680                     final int oneLength = 10 + random.nextInt(20);
681                     for (int k = 0; k < oneLength; k++) {
682                         intPool.addObject(KEY_ONE);
683                     }
684                     final int twoLength = 10 + random.nextInt(20);
685                     for (int k = 0; k < twoLength; k++) {
686                         intPool.addObject(KEY_TWO);
687                     }
688                     // Choose a random number of evictor runs
689                     final int runs = 10 + random.nextInt(50);
690                     for (int k = 0; k < runs; k++) {
691                         intPool.evict();
692                     }
693                     // Total instances in pool
694                     final int totalInstances = zeroLength + oneLength + twoLength;
695                     // Number of times evictor should have cycled through pools
696                     final int cycleCount = runs * intPool.getNumTestsPerEvictionRun() / totalInstances;
697                     // Look at elements and make sure they are visited cycleCount
698                     // or cycleCount + 1 times
699                     VisitTracker<Integer> tracker = null;
700                     int visitCount = 0;
701                     for (int k = 0; k < zeroLength; k++) {
702                         tracker = intPool.borrowObject(KEY_ZERO);
703                         visitCount = tracker.getValidateCount();
704                         if (visitCount < cycleCount || visitCount > cycleCount + 1) {
705                             fail(formatSettings("ZERO", "runs", runs, "lifo", lifo, "i", i, "j", j, "k", k, "visitCount", visitCount, "cycleCount", cycleCount,
706                                     "totalInstances", totalInstances, zeroLength, oneLength, twoLength));
707                         }
708                     }
709                     for (int k = 0; k < oneLength; k++) {
710                         tracker = intPool.borrowObject(KEY_ONE);
711                         visitCount = tracker.getValidateCount();
712                         if (visitCount < cycleCount || visitCount > cycleCount + 1) {
713                             fail(formatSettings("ONE", "runs", runs, "lifo", lifo, "i", i, "j", j, "k", k, "visitCount", visitCount, "cycleCount", cycleCount,
714                                     "totalInstances", totalInstances, zeroLength, oneLength, twoLength));
715                         }
716                     }
717                     final int[] visits = new int[twoLength];
718                     for (int k = 0; k < twoLength; k++) {
719                         tracker = intPool.borrowObject(KEY_TWO);
720                         visitCount = tracker.getValidateCount();
721                         visits[k] = visitCount;
722                         if (visitCount < cycleCount || visitCount > cycleCount + 1) {
723                             final StringBuilder sb = new StringBuilder("Visits:");
724                             for (int l = 0; l <= k; l++) {
725                                 sb.append(visits[l]).append(' ');
726                             }
727                             fail(formatSettings("TWO " + sb.toString(), "runs", runs, "lifo", lifo, "i", i, "j", j, "k", k, "visitCount", visitCount,
728                                     "cycleCount", cycleCount, "totalInstances", totalInstances, zeroLength, oneLength, twoLength));
729                         }
730                     }
731                 }
732             }
733         }
734     }
735 
736     private String formatSettings(final String title, final String s, final int i, final String s0, final boolean b0, final String s1, final int i1,
737             final String s2, final int i2, final String s3, final int i3, final String s4, final int i4, final String s5, final int i5, final String s6,
738             final int i6, final int zeroLength, final int oneLength, final int twoLength) {
739         final StringBuilder sb = new StringBuilder(80);
740         sb.append(title).append(' ');
741         sb.append(s).append('=').append(i).append(' ');
742         sb.append(s0).append('=').append(b0).append(' ');
743         sb.append(s1).append('=').append(i1).append(' ');
744         sb.append(s2).append('=').append(i2).append(' ');
745         sb.append(s3).append('=').append(i3).append(' ');
746         sb.append(s4).append('=').append(i4).append(' ');
747         sb.append(s5).append('=').append(i5).append(' ');
748         sb.append(s6).append('=').append(i6).append(' ');
749         sb.append("Lengths=").append(zeroLength).append(',').append(oneLength).append(',').append(twoLength).append(' ');
750         return sb.toString();
751     }
752 
753     @Override
754     protected Object getNthObject(final Object key, final int n) {
755         return String.valueOf(key) + String.valueOf(n);
756     }
757 
758     @Override
759     protected boolean isFifo() {
760         return false;
761     }
762 
763     @Override
764     protected boolean isLifo() {
765         return true;
766     }
767 
768     @SuppressWarnings("unchecked")
769     @Override
770     protected <E extends Exception> KeyedObjectPool<Object, Object> makeEmptyPool(final int minCapacity) {
771         final KeyedPooledObjectFactory<Object, Object> perKeyFactory = new SimplePerKeyFactory();
772         final GenericKeyedObjectPool<Object, Object> perKeyPool = new GenericKeyedObjectPool<>(perKeyFactory);
773         perKeyPool.setMaxTotalPerKey(minCapacity);
774         perKeyPool.setMaxIdlePerKey(minCapacity);
775         return perKeyPool;
776     }
777 
778     @Override
779     protected <E extends Exception> KeyedObjectPool<Object, Object> makeEmptyPool(final KeyedPooledObjectFactory<Object, Object> fac) {
780         return new GenericKeyedObjectPool<>(fac);
781     }
782 
783     @Override
784     protected Object makeKey(final int n) {
785         return String.valueOf(n);
786     }
787 
788     /**
789      * Kicks off {@code numThreads} test threads, each of which will go through {@code iterations} borrow-return cycles with random delay times &lt;= delay in
790      * between.
791      *
792      * @param <T>        Type of object in pool
793      * @param numThreads Number of test threads
794      * @param iterations Number of iterations for each thread
795      * @param delay      Maximum delay between iterations
796      * @param gkopPool   The keyed object pool to use
797      */
798     public <T> void runTestThreads(final int numThreads, final int iterations, final int delay, final GenericKeyedObjectPool<String, T> gkopPool) {
799         final ArrayList<TestThread<T>> threads = new ArrayList<>();
800         for (int i = 0; i < numThreads; i++) {
801             final TestThread<T> testThread = new TestThread<>(gkopPool, iterations, delay);
802             threads.add(testThread);
803             final Thread t = new Thread(testThread);
804             t.start();
805         }
806         for (final TestThread<T> testThread : threads) {
807             while (!testThread.complete()) {
808                 Waiter.sleepQuietly(500L);
809             }
810             if (testThread.failed()) {
811                 fail("Thread failed: " + threads.indexOf(testThread) + "\n" + ExceptionUtils.getStackTrace(testThread.exception));
812             }
813         }
814     }
815 
816     @BeforeEach
817     public void setUp() {
818         simpleFactory = new SimpleFactory<>();
819         gkoPool = new GenericKeyedObjectPool<>(simpleFactory);
820     }
821 
822     @AfterEach
823     public void tearDownJmx() throws Exception {
824         super.tearDown();
825         final ObjectName jmxName = gkoPool.getJmxName();
826         final String poolName = Objects.toString(jmxName, null);
827         gkoPool.clear();
828         gkoPool.close();
829         gkoPool = null;
830         simpleFactory = null;
831         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
832         final Set<ObjectName> result = mbs.queryNames(new ObjectName("org.apache.commoms.pool2:type=GenericKeyedObjectPool,*"), null);
833         // There should be no registered pools at this point
834         final int registeredPoolCount = result.size();
835         final StringBuilder msg = new StringBuilder("Current pool is: ");
836         msg.append(poolName);
837         msg.append("  Still open pools are: ");
838         for (final ObjectName name : result) {
839             // Clean these up ready for the next test
840             msg.append(name.toString());
841             msg.append(" created via\n");
842             msg.append(mbs.getAttribute(name, "CreationStackTrace"));
843             msg.append('\n');
844             mbs.unregisterMBean(name);
845         }
846         assertEquals(0, registeredPoolCount, msg.toString());
847     }
848 
849     @Test
850     void testAppendStats() {
851         assertFalse(gkoPool.getMessageStatistics());
852         assertEquals("foo", gkoPool.appendStats("foo"));
853         try (GenericKeyedObjectPool<?, ?> pool = new GenericKeyedObjectPool<>(new SimpleFactory<>())) {
854             pool.setMessagesStatistics(true);
855             assertNotEquals("foo", pool.appendStats("foo"));
856             pool.setMessagesStatistics(false);
857             assertEquals("foo", pool.appendStats("foo"));
858         }
859     }
860 
861     @Test
862     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
863     void testBlockedKeyDoesNotBlockPool() throws Exception {
864         gkoPool.setBlockWhenExhausted(true);
865         gkoPool.setMaxWaitMillis(5000);
866         gkoPool.setMaxTotalPerKey(1);
867         gkoPool.setMaxTotal(-1);
868         gkoPool.borrowObject("one");
869         final long startMillis = System.currentTimeMillis();
870         // Needs to be in a separate thread as this will block
871         final Runnable simple = new SimpleTestThread<>(gkoPool, "one");
872         new Thread(simple).start();
873         // This should be almost instant. If it isn't it means this thread got
874         // stuck behind the thread created above which is bad.
875         // Give other thread a chance to start
876         Thread.sleep(1000);
877         gkoPool.borrowObject("two");
878         final long endMillis = System.currentTimeMillis();
879         // If it fails it will be more than 4000ms (5000 less the 1000 sleep)
880         // If it passes it should be almost instant
881         // Use 3000ms as the threshold - should avoid timing issues on most
882         // (all? platforms)
883         assertTrue(endMillis - startMillis < 4000, "Elapsed time: " + (endMillis - startMillis) + " should be less than 4000");
884     }
885 
886     /*
887      * 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
888      * this if you see a failure of this test.
889      */
890     @SuppressWarnings("rawtypes")
891     @Test
892     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
893     void testBorrowObjectFairness() throws Exception {
894         final int numThreads = 40;
895         final int maxTotal = 40;
896         final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>();
897         config.setMaxTotalPerKey(maxTotal);
898         config.setFairness(true);
899         config.setLifo(false);
900         config.setMaxIdlePerKey(maxTotal);
901         gkoPool = new GenericKeyedObjectPool<>(simpleFactory, config);
902         // Exhaust the pool
903         final String[] objects = new String[maxTotal];
904         for (int i = 0; i < maxTotal; i++) {
905             objects[i] = gkoPool.borrowObject("0");
906         }
907         // Start and park threads waiting to borrow objects
908         final TestThread[] threads = new TestThread[numThreads];
909         for (int i = 0; i < numThreads; i++) {
910             threads[i] = new TestThread<>(gkoPool, 1, 0, 2000, false, "0" + String.valueOf(i % maxTotal), "0");
911             final Thread t = new Thread(threads[i]);
912             t.start();
913             // Short delay to ensure threads start in correct order
914             try {
915                 Thread.sleep(10);
916             } catch (final InterruptedException e) {
917                 fail(e.toString());
918             }
919         }
920         // Return objects, other threads should get served in order
921         for (int i = 0; i < maxTotal; i++) {
922             gkoPool.returnObject("0", objects[i]);
923         }
924         // Wait for threads to finish
925         for (int i = 0; i < numThreads; i++) {
926             while (!threads[i].complete()) {
927                 Waiter.sleepQuietly(500L);
928             }
929             if (threads[i].failed()) {
930                 fail("Thread " + i + " failed: " + ExceptionUtils.getStackTrace(threads[i].exception));
931             }
932         }
933     }
934 
935     /**
936      * POOL-192 Verify that clear(key) does not leak capacity.
937      *
938      * @throws Exception May occur in some failure modes
939      */
940     @Test
941     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
942     void testClear() throws Exception {
943         gkoPool.setMaxTotal(2);
944         gkoPool.setMaxTotalPerKey(2);
945         gkoPool.setBlockWhenExhausted(false);
946         gkoPool.addObject("one");
947         gkoPool.addObject("one");
948         assertEquals(2, gkoPool.getNumIdle());
949         gkoPool.clear("one");
950         assertEquals(0, gkoPool.getNumIdle());
951         assertEquals(0, gkoPool.getNumIdle("one"));
952         final String obj1 = gkoPool.borrowObject("one");
953         final String obj2 = gkoPool.borrowObject("one");
954         gkoPool.returnObject("one", obj1);
955         gkoPool.returnObject("one", obj2);
956         gkoPool.clear();
957         assertEquals(0, gkoPool.getNumIdle());
958         assertEquals(0, gkoPool.getNumIdle("one"));
959         gkoPool.borrowObject("one");
960         gkoPool.borrowObject("one");
961         gkoPool.close();
962     }
963 
964     /**
965      * Test to make sure that clearOldest does not destroy instances that have been checked out.
966      *
967      * @throws Exception May occur in some failure modes
968      */
969     @Test
970     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
971     void testClearOldest() throws Exception {
972         // Make destroy have some latency so clearOldest takes some time
973         final WaiterFactory<String> waiterFactory = new WaiterFactory<>(0, 20, 0, 0, 0, 0, 50, 5, 0);
974         try (GenericKeyedObjectPool<String, Waiter> waiterPool = new GenericKeyedObjectPool<>(waiterFactory)) {
975             waiterPool.setMaxTotalPerKey(5);
976             waiterPool.setMaxTotal(50);
977             waiterPool.setLifo(false);
978             // Load the pool with idle instances - 5 each for 10 keys
979             for (int i = 0; i < 10; i++) {
980                 final String key = Integer.toString(i);
981                 for (int j = 0; j < 5; j++) {
982                     waiterPool.addObject(key);
983                 }
984                 // Make sure order is maintained
985                 Thread.sleep(20);
986             }
987             // Now set up a race - one thread wants a new instance, triggering clearOldest
988             // Other goes after an element on death row
989             // See if we end up with dead man walking
990             final SimpleTestThread<Waiter> t2 = new SimpleTestThread<>(waiterPool, "51");
991             final Thread thread2 = new Thread(t2);
992             thread2.start(); // Triggers clearOldest, killing all of the 0's and the 2 oldest 1's
993             Thread.sleep(50); // Wait for clearOldest to kick off, but not long enough to reach the 1's
994             final Waiter waiter = waiterPool.borrowObject("1");
995             Thread.sleep(200); // Wait for execution to happen
996             waiterPool.returnObject("1", waiter); // Will throw IllegalStateException if dead
997         }
998     }
999 
1000     /**
1001      * POOL-391 Verify that when clear(key) is called with reuseCapacity true, capacity freed is reused and allocated to most loaded pools.
1002      *
1003      * @throws Exception May occur in some failure modes
1004      */
1005     @Test
1006     void testClearReuseCapacity() throws Exception {
1007         gkoPool.setMaxTotalPerKey(6);
1008         gkoPool.setMaxTotal(6);
1009         gkoPool.setMaxWait(Duration.ofSeconds(5));
1010         // Create one thread to wait on "one", two on "two", three on "three"
1011         final ArrayList<Thread> testThreads = new ArrayList<>();
1012         testThreads.add(new Thread(new SimpleTestThread<>(gkoPool, "one")));
1013         testThreads.add(new Thread(new SimpleTestThread<>(gkoPool, "two")));
1014         testThreads.add(new Thread(new SimpleTestThread<>(gkoPool, "two")));
1015         testThreads.add(new Thread(new SimpleTestThread<>(gkoPool, "three")));
1016         testThreads.add(new Thread(new SimpleTestThread<>(gkoPool, "three")));
1017         testThreads.add(new Thread(new SimpleTestThread<>(gkoPool, "three")));
1018         // Borrow two each from "four", "five", "six" - using all capacity
1019         final String four = gkoPool.borrowObject("four");
1020         final String four2 = gkoPool.borrowObject("four");
1021         final String five = gkoPool.borrowObject("five");
1022         final String five2 = gkoPool.borrowObject("five");
1023         final String six = gkoPool.borrowObject("six");
1024         final String six2 = gkoPool.borrowObject("six");
1025         Thread.sleep(100);
1026         // Launch the waiters - all will be blocked waiting
1027         for (final Thread t : testThreads) {
1028             t.start();
1029         }
1030         Thread.sleep(100);
1031         // Return and clear the fours - at least one "three" should get served
1032         // Other should be a two or a three (three was most loaded)
1033         gkoPool.returnObject("four", four);
1034         gkoPool.returnObject("four", four2);
1035         gkoPool.clear("four");
1036         Thread.sleep(20);
1037         assertTrue(!testThreads.get(3).isAlive() || !testThreads.get(4).isAlive() || !testThreads.get(5).isAlive());
1038         // Now clear the fives
1039         gkoPool.returnObject("five", five);
1040         gkoPool.returnObject("five", five2);
1041         gkoPool.clear("five");
1042         Thread.sleep(20);
1043         // Clear the sixes
1044         gkoPool.returnObject("six", six);
1045         gkoPool.returnObject("six", six2);
1046         gkoPool.clear("six");
1047         Thread.sleep(20);
1048         for (final Thread t : testThreads) {
1049             assertFalse(t.isAlive());
1050         }
1051     }
1052 
1053     /**
1054      * POOL-391 Adapted from code in the JIRA ticket.
1055      */
1056     @Test
1057     @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
1058     void testClearUnblocksWaiters() throws Exception {
1059         final GenericKeyedObjectPoolConfig<Integer> config = new GenericKeyedObjectPoolConfig<>();
1060         config.setMaxTotalPerKey(1);
1061         config.setMaxTotal(1);
1062         config.setMaxWait(Duration.ofMillis(500));
1063         try (GenericKeyedObjectPool<Integer, Integer> testPool = new GenericKeyedObjectPool<>(new KeyedPooledObjectFactory<Integer, Integer>() {
1064 
1065             @Override
1066             public void activateObject(final Integer key, final PooledObject<Integer> p) {
1067                 // do nothing
1068             }
1069 
1070             @Override
1071             public void destroyObject(final Integer key, final PooledObject<Integer> p) throws InterruptedException {
1072                 Thread.sleep(10);
1073             }
1074 
1075             @Override
1076             public PooledObject<Integer> makeObject(final Integer key) {
1077                 return new DefaultPooledObject<>(10);
1078             }
1079 
1080             @Override
1081             public void passivateObject(final Integer key, final PooledObject<Integer> p) {
1082                 // do nothing
1083             }
1084 
1085             @Override
1086             public boolean validateObject(final Integer key, final PooledObject<Integer> p) {
1087                 return true;
1088             }
1089         }, config)) {
1090             final Integer borrowKey = 10;
1091             final int iterations = 100;
1092             final ExecutorService executor = Executors.newFixedThreadPool(2);
1093             final Thread t = new Thread(() -> {
1094                 try {
1095                     for (int i = 0; i < iterations; i++) {
1096                         final Integer integer = testPool.borrowObject(borrowKey);
1097                         testPool.returnObject(borrowKey, integer);
1098                         Thread.sleep(10);
1099                     }
1100                 } catch (final Exception e) {
1101                     fail(e);
1102                 }
1103             });
1104             final Thread t2 = new Thread(() -> {
1105                 try {
1106                     for (int i = 0; i < iterations; i++) {
1107                         testPool.clear(borrowKey);
1108                         Thread.sleep(10);
1109                     }
1110                 } catch (final Exception e) {
1111                     fail(e);
1112                 }
1113             });
1114             final Future<?> f1 = executor.submit(t);
1115             final Future<?> f2 = executor.submit(t2);
1116             f2.get();
1117             f1.get();
1118         }
1119     }
1120 
1121     // POOL-259
1122     @Test
1123     void testClientWaitStats() throws Exception {
1124         final SimpleFactory<String> factory = new SimpleFactory<>();
1125         // Give makeObject a little latency
1126         factory.setMakeLatency(200);
1127         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(factory, new GenericKeyedObjectPoolConfig<>())) {
1128             final String s = pool.borrowObject("one");
1129             // First borrow waits on create, so wait time should be at least 200 ms
1130             // Allow 100ms error in clock times
1131             assertTrue(pool.getMaxBorrowWaitTimeMillis() >= 100);
1132             assertTrue(pool.getMeanBorrowWaitTimeMillis() >= 100);
1133             pool.returnObject("one", s);
1134             pool.borrowObject("one");
1135             // Second borrow does not have to wait on create, average should be about 100
1136             assertTrue(pool.getMaxBorrowWaitTimeMillis() > 100);
1137             assertTrue(pool.getMeanBorrowWaitTimeMillis() < 200);
1138             assertTrue(pool.getMeanBorrowWaitTimeMillis() > 20);
1139         }
1140     }
1141 
1142     /**
1143      * Tests POOL-411, or least tries to reproduce the NPE, but does not.
1144      *
1145      * @throws TestException a test failure.
1146      */
1147     @Test
1148     void testConcurrentBorrowAndClear() throws Exception {
1149         final int threadCount = 64;
1150         final int taskCount = 64;
1151         final int addCount = 1;
1152         final int borrowCycles = 1024;
1153         final int clearCycles = 1024;
1154         final boolean useYield = true;
1155         testConcurrentBorrowAndClear(threadCount, taskCount, addCount, borrowCycles, clearCycles, useYield);
1156     }
1157 
1158     /*
1159      * Tests POOL-411, or least tries to reproduce the NPE, but does not.
1160      *
1161      * @throws Exception a test failure.
1162      */
1163     private void testConcurrentBorrowAndClear(final int threadCount, final int taskCount, final int addCount, final int borrowCycles, final int clearCycles,
1164             final boolean useYield) throws Exception {
1165         final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>();
1166         final int maxTotalPerKey = borrowCycles + 1;
1167         config.setMaxTotalPerKey(threadCount);
1168         config.setMaxIdlePerKey(threadCount);
1169         config.setMaxTotal(maxTotalPerKey * threadCount);
1170         config.setBlockWhenExhausted(false); // pool exhausted indicates a bug in the test
1171         gkoPool = new GenericKeyedObjectPool<>(simpleFactory, config);
1172         final String key = "0";
1173         gkoPool.addObjects(Arrays.asList(key), threadCount);
1174         // all objects in the pool are now idle.
1175         final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
1176         final List<Future<?>> futures = new ArrayList<>();
1177         try {
1178             for (int t = 0; t < taskCount; t++) {
1179                 futures.add(threadPool.submit(() -> {
1180                     for (int i = 0; i < clearCycles; i++) {
1181                         if (useYield) {
1182                             Thread.yield();
1183                         }
1184                         gkoPool.clear(key, true);
1185                         try {
1186                             gkoPool.addObjects(Arrays.asList(key), addCount);
1187                         } catch (final Exception e) {
1188                             fail(e);
1189                         }
1190                     }
1191                 }));
1192                 futures.add(threadPool.submit(() -> {
1193                     try {
1194                         for (int i = 0; i < borrowCycles; i++) {
1195                             if (useYield) {
1196                                 Thread.yield();
1197                             }
1198                             final String pooled = gkoPool.borrowObject(key);
1199                             gkoPool.returnObject(key, pooled);
1200                         }
1201                     } catch (final Exception e) {
1202                         fail(e);
1203                     }
1204                 }));
1205             }
1206             futures.forEach(f -> {
1207                 try {
1208                     f.get();
1209                 } catch (InterruptedException | ExecutionException e) {
1210                     fail(e);
1211                 }
1212             });
1213         } finally {
1214             threadPool.shutdownNow();
1215         }
1216     }
1217 
1218     /**
1219      * See https://issues.apache.org/jira/browse/POOL-411?
1220      * focusedCommentId=17741156&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-17741156
1221      *
1222      * @throws Exception a test failure.
1223      */
1224     @Test
1225     void testConcurrentBorrowAndClear_JiraComment17741156() throws Exception {
1226         final int threadCount = 2;
1227         final int taskCount = 2;
1228         final int addCount = 1;
1229         final int borrowCycles = 5_000;
1230         final int clearCycles = 5_000;
1231         final boolean useYield = false;
1232         testConcurrentBorrowAndClear(threadCount, taskCount, addCount, borrowCycles, clearCycles, useYield);
1233     }
1234 
1235     /**
1236      * POOL-231 - verify that concurrent invalidates of the same object do not corrupt pool destroyCount.
1237      *
1238      * @throws Exception May occur in some failure modes
1239      */
1240     @Test
1241     void testConcurrentInvalidate() throws Exception {
1242         // Get allObjects and idleObjects loaded with some instances
1243         final int nObjects = 1000;
1244         final String key = "one";
1245         gkoPool.setMaxTotal(nObjects);
1246         gkoPool.setMaxTotalPerKey(nObjects);
1247         gkoPool.setMaxIdlePerKey(nObjects);
1248         final String[] obj = new String[nObjects];
1249         for (int i = 0; i < nObjects; i++) {
1250             obj[i] = gkoPool.borrowObject(key);
1251         }
1252         for (int i = 0; i < nObjects; i++) {
1253             if (i % 2 == 0) {
1254                 gkoPool.returnObject(key, obj[i]);
1255             }
1256         }
1257         final int nThreads = 20;
1258         final int nIterations = 60;
1259         final InvalidateThread[] threads = new InvalidateThread[nThreads];
1260         // Randomly generated list of distinct invalidation targets
1261         final ArrayList<Integer> targets = new ArrayList<>();
1262         final Random random = new Random();
1263         for (int j = 0; j < nIterations; j++) {
1264             // Get a random invalidation target
1265             Integer targ = Integer.valueOf(random.nextInt(nObjects));
1266             while (targets.contains(targ)) {
1267                 targ = Integer.valueOf(random.nextInt(nObjects));
1268             }
1269             targets.add(targ);
1270             // Launch nThreads threads all trying to invalidate the target
1271             for (int i = 0; i < nThreads; i++) {
1272                 threads[i] = new InvalidateThread(gkoPool, key, obj[targ.intValue()]);
1273             }
1274             for (int i = 0; i < nThreads; i++) {
1275                 new Thread(threads[i]).start();
1276             }
1277             boolean done = false;
1278             while (!done) {
1279                 done = true;
1280                 for (int i = 0; i < nThreads; i++) {
1281                     done = done && threads[i].complete();
1282                 }
1283                 Thread.sleep(100);
1284             }
1285         }
1286         assertEquals(nIterations, gkoPool.getDestroyedCount());
1287     }
1288 
1289     @Test
1290     void testConstructorNullFactory() {
1291         // add dummy assert (won't be invoked because of IAE) to avoid "unused" warning
1292         assertThrows(IllegalArgumentException.class, () -> new GenericKeyedObjectPool<>(null));
1293     }
1294 
1295     @SuppressWarnings("deprecation")
1296     @Test
1297     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1298     void testConstructors() {
1299         // Make constructor arguments all different from defaults
1300         final int maxTotalPerKey = 1;
1301         final int minIdle = 2;
1302         final Duration maxWaitDuration = Duration.ofMillis(3);
1303         final long maxWaitMillis = maxWaitDuration.toMillis();
1304         final int maxIdle = 4;
1305         final int maxTotal = 5;
1306         final long minEvictableIdleTimeMillis = 6;
1307         final int numTestsPerEvictionRun = 7;
1308         final boolean testOnBorrow = true;
1309         final boolean testOnReturn = true;
1310         final boolean testWhileIdle = true;
1311         final long timeBetweenEvictionRunsMillis = 8;
1312         final boolean blockWhenExhausted = false;
1313         final boolean lifo = false;
1314         final KeyedPooledObjectFactory<Object, Object> dummyFactory = new DummyFactory();
1315         try (GenericKeyedObjectPool<Object, Object> objPool = new GenericKeyedObjectPool<>(dummyFactory)) {
1316             assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY, objPool.getMaxTotalPerKey());
1317             assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY, objPool.getMaxIdlePerKey());
1318             assertEquals(BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS, objPool.getMaxWaitMillis());
1319             assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY, objPool.getMinIdlePerKey());
1320             assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL, objPool.getMaxTotal());
1321             //
1322             assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS, objPool.getMinEvictableIdleTimeMillis());
1323             assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME, objPool.getMinEvictableIdleTime());
1324             assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME, objPool.getMinEvictableIdleDuration());
1325             //
1326             assertEquals(BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN, objPool.getNumTestsPerEvictionRun());
1327             assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW), Boolean.valueOf(objPool.getTestOnBorrow()));
1328             assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN), Boolean.valueOf(objPool.getTestOnReturn()));
1329             assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE), Boolean.valueOf(objPool.getTestWhileIdle()));
1330             //
1331             assertEquals(BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS, objPool.getDurationBetweenEvictionRuns());
1332             assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS, objPool.getTimeBetweenEvictionRunsMillis());
1333             assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS, objPool.getTimeBetweenEvictionRuns());
1334             //
1335             assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED), Boolean.valueOf(objPool.getBlockWhenExhausted()));
1336             assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_LIFO), Boolean.valueOf(objPool.getLifo()));
1337         }
1338         final GenericKeyedObjectPoolConfig<Object> config = new GenericKeyedObjectPoolConfig<>();
1339         config.setLifo(lifo);
1340         config.setMaxTotalPerKey(maxTotalPerKey);
1341         config.setMaxIdlePerKey(maxIdle);
1342         config.setMinIdlePerKey(minIdle);
1343         config.setMaxTotal(maxTotal);
1344         config.setMaxWait(maxWaitDuration);
1345         config.setMinEvictableIdleDuration(Duration.ofMillis(minEvictableIdleTimeMillis));
1346         config.setMinEvictableIdleTime(Duration.ofMillis(minEvictableIdleTimeMillis));
1347         config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
1348         config.setTestOnBorrow(testOnBorrow);
1349         config.setTestOnReturn(testOnReturn);
1350         config.setTestWhileIdle(testWhileIdle);
1351         config.setTimeBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRunsMillis));
1352         config.setBlockWhenExhausted(blockWhenExhausted);
1353         try (GenericKeyedObjectPool<Object, Object> objPool = new GenericKeyedObjectPool<>(dummyFactory, config)) {
1354             assertEquals(maxTotalPerKey, objPool.getMaxTotalPerKey());
1355             assertEquals(maxIdle, objPool.getMaxIdlePerKey());
1356             assertEquals(maxWaitDuration, objPool.getMaxWaitDuration());
1357             assertEquals(maxWaitMillis, objPool.getMaxWaitMillis());
1358             assertEquals(minIdle, objPool.getMinIdlePerKey());
1359             assertEquals(maxTotal, objPool.getMaxTotal());
1360             assertEquals(minEvictableIdleTimeMillis, objPool.getMinEvictableIdleDuration().toMillis());
1361             assertEquals(minEvictableIdleTimeMillis, objPool.getMinEvictableIdleTimeMillis());
1362             assertEquals(minEvictableIdleTimeMillis, objPool.getMinEvictableIdleTime().toMillis());
1363             assertEquals(numTestsPerEvictionRun, objPool.getNumTestsPerEvictionRun());
1364             assertEquals(Boolean.valueOf(testOnBorrow), Boolean.valueOf(objPool.getTestOnBorrow()));
1365             assertEquals(Boolean.valueOf(testOnReturn), Boolean.valueOf(objPool.getTestOnReturn()));
1366             assertEquals(Boolean.valueOf(testWhileIdle), Boolean.valueOf(objPool.getTestWhileIdle()));
1367             assertEquals(timeBetweenEvictionRunsMillis, objPool.getDurationBetweenEvictionRuns().toMillis());
1368             assertEquals(timeBetweenEvictionRunsMillis, objPool.getTimeBetweenEvictionRunsMillis());
1369             assertEquals(timeBetweenEvictionRunsMillis, objPool.getTimeBetweenEvictionRuns().toMillis());
1370             assertEquals(Boolean.valueOf(blockWhenExhausted), Boolean.valueOf(objPool.getBlockWhenExhausted()));
1371             assertEquals(Boolean.valueOf(lifo), Boolean.valueOf(objPool.getLifo()));
1372         }
1373     }
1374 
1375     /**
1376      * JIRA: POOL-270 - make sure constructor correctly sets run frequency of evictor timer.
1377      */
1378     @Test
1379     void testContructorEvictionConfig() throws Exception {
1380         final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>();
1381         config.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
1382         config.setMinEvictableIdleDuration(Duration.ofMillis(50));
1383         config.setNumTestsPerEvictionRun(5);
1384         try (GenericKeyedObjectPool<String, String> p = new GenericKeyedObjectPool<>(simpleFactory, config)) {
1385             for (int i = 0; i < 5; i++) {
1386                 p.addObject("one");
1387             }
1388             Waiter.sleepQuietly(100);
1389             assertEquals(5, p.getNumIdle("one"));
1390             Waiter.sleepQuietly(500);
1391             assertEquals(0, p.getNumIdle("one"));
1392         }
1393     }
1394 
1395     /**
1396      * Verifies that when a factory's makeObject produces instances that are not discernible by equals, the pool can handle them.
1397      *
1398      * JIRA: POOL-283
1399      */
1400     @Test
1401     void testEqualsIndiscernible() throws Exception {
1402         final HashSetFactory factory = new HashSetFactory();
1403         try (GenericKeyedObjectPool<String, HashSet<String>> pool = new GenericKeyedObjectPool<>(factory, new GenericKeyedObjectPoolConfig<>())) {
1404             final HashSet<String> s1 = pool.borrowObject("a");
1405             final HashSet<String> s2 = pool.borrowObject("a");
1406             pool.returnObject("a", s1);
1407             pool.returnObject("a", s2);
1408         }
1409     }
1410 
1411     @Test
1412     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1413     void testEviction() throws Exception {
1414         gkoPool.setMaxIdlePerKey(500);
1415         gkoPool.setMaxTotalPerKey(500);
1416         gkoPool.setNumTestsPerEvictionRun(100);
1417         gkoPool.setMinEvictableIdleTime(Duration.ofMillis(250));
1418         gkoPool.setDurationBetweenEvictionRuns(Duration.ofMillis(500));
1419         final String[] active = new String[500];
1420         for (int i = 0; i < 500; i++) {
1421             active[i] = gkoPool.borrowObject("");
1422         }
1423         for (int i = 0; i < 500; i++) {
1424             gkoPool.returnObject("", active[i]);
1425         }
1426         Waiter.sleepQuietly(1000L);
1427         assertTrue(gkoPool.getNumIdle("") < 500, "Should be less than 500 idle, found " + gkoPool.getNumIdle(""));
1428         Waiter.sleepQuietly(600L);
1429         assertTrue(gkoPool.getNumIdle("") < 400, "Should be less than 400 idle, found " + gkoPool.getNumIdle(""));
1430         Waiter.sleepQuietly(600L);
1431         assertTrue(gkoPool.getNumIdle("") < 300, "Should be less than 300 idle, found " + gkoPool.getNumIdle(""));
1432         Waiter.sleepQuietly(600L);
1433         assertTrue(gkoPool.getNumIdle("") < 200, "Should be less than 200 idle, found " + gkoPool.getNumIdle(""));
1434         Waiter.sleepQuietly(600L);
1435         assertTrue(gkoPool.getNumIdle("") < 100, "Should be less than 100 idle, found " + gkoPool.getNumIdle(""));
1436         Waiter.sleepQuietly(600L);
1437         assertEquals(0, gkoPool.getNumIdle(""), "Should be zero idle, found " + gkoPool.getNumIdle(""));
1438         for (int i = 0; i < 500; i++) {
1439             active[i] = gkoPool.borrowObject("");
1440         }
1441         for (int i = 0; i < 500; i++) {
1442             gkoPool.returnObject("", active[i]);
1443         }
1444         Waiter.sleepQuietly(1000L);
1445         assertTrue(gkoPool.getNumIdle("") < 500, "Should be less than 500 idle, found " + gkoPool.getNumIdle(""));
1446         Waiter.sleepQuietly(600L);
1447         assertTrue(gkoPool.getNumIdle("") < 400, "Should be less than 400 idle, found " + gkoPool.getNumIdle(""));
1448         Waiter.sleepQuietly(600L);
1449         assertTrue(gkoPool.getNumIdle("") < 300, "Should be less than 300 idle, found " + gkoPool.getNumIdle(""));
1450         Waiter.sleepQuietly(600L);
1451         assertTrue(gkoPool.getNumIdle("") < 200, "Should be less than 200 idle, found " + gkoPool.getNumIdle(""));
1452         Waiter.sleepQuietly(600L);
1453         assertTrue(gkoPool.getNumIdle("") < 100, "Should be less than 100 idle, found " + gkoPool.getNumIdle(""));
1454         Waiter.sleepQuietly(600L);
1455         assertEquals(0, gkoPool.getNumIdle(""), "Should be zero idle, found " + gkoPool.getNumIdle(""));
1456     }
1457 
1458     @Test
1459     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1460     void testEviction2() throws Exception {
1461         gkoPool.setMaxIdlePerKey(500);
1462         gkoPool.setMaxTotalPerKey(500);
1463         gkoPool.setNumTestsPerEvictionRun(100);
1464         gkoPool.setMinEvictableIdleTime(Duration.ofMillis(500));
1465         gkoPool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
1466         final String[] active = new String[500];
1467         final String[] active2 = new String[500];
1468         for (int i = 0; i < 500; i++) {
1469             active[i] = gkoPool.borrowObject("");
1470             active2[i] = gkoPool.borrowObject("2");
1471         }
1472         for (int i = 0; i < 500; i++) {
1473             gkoPool.returnObject("", active[i]);
1474             gkoPool.returnObject("2", active2[i]);
1475         }
1476         Waiter.sleepQuietly(1100L);
1477         assertTrue(gkoPool.getNumIdle() < 1000, "Should be less than 1000 idle, found " + gkoPool.getNumIdle());
1478         final long sleepMillisPart2 = 600L;
1479         Waiter.sleepQuietly(sleepMillisPart2);
1480         assertTrue(gkoPool.getNumIdle() < 900, "Should be less than 900 idle, found " + gkoPool.getNumIdle());
1481         Waiter.sleepQuietly(sleepMillisPart2);
1482         assertTrue(gkoPool.getNumIdle() < 800, "Should be less than 800 idle, found " + gkoPool.getNumIdle());
1483         Waiter.sleepQuietly(sleepMillisPart2);
1484         assertTrue(gkoPool.getNumIdle() < 700, "Should be less than 700 idle, found " + gkoPool.getNumIdle());
1485         Waiter.sleepQuietly(sleepMillisPart2);
1486         assertTrue(gkoPool.getNumIdle() < 600, "Should be less than 600 idle, found " + gkoPool.getNumIdle());
1487         Waiter.sleepQuietly(sleepMillisPart2);
1488         assertTrue(gkoPool.getNumIdle() < 500, "Should be less than 500 idle, found " + gkoPool.getNumIdle());
1489         Waiter.sleepQuietly(sleepMillisPart2);
1490         assertTrue(gkoPool.getNumIdle() < 400, "Should be less than 400 idle, found " + gkoPool.getNumIdle());
1491         Waiter.sleepQuietly(sleepMillisPart2);
1492         assertTrue(gkoPool.getNumIdle() < 300, "Should be less than 300 idle, found " + gkoPool.getNumIdle());
1493         Waiter.sleepQuietly(sleepMillisPart2);
1494         assertTrue(gkoPool.getNumIdle() < 200, "Should be less than 200 idle, found " + gkoPool.getNumIdle());
1495         Waiter.sleepQuietly(sleepMillisPart2);
1496         assertTrue(gkoPool.getNumIdle() < 100, "Should be less than 100 idle, found " + gkoPool.getNumIdle());
1497         Waiter.sleepQuietly(sleepMillisPart2);
1498         assertEquals(0, gkoPool.getNumIdle(), "Should be zero idle, found " + gkoPool.getNumIdle());
1499     }
1500 
1501     /**
1502      * Test to make sure evictor visits least recently used objects first, regardless of FIFO/LIFO
1503      *
1504      * JIRA: POOL-86
1505      *
1506      * @throws Exception May occur in some failure modes
1507      */
1508     @Test
1509     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1510     void testEvictionOrder() throws Exception {
1511         checkEvictionOrder(false);
1512         checkEvictionOrder(true);
1513     }
1514 
1515     // POOL-326
1516     @Test
1517     void testEvictorClearOldestRace() throws Exception {
1518         gkoPool.setMinEvictableIdleTime(Duration.ofMillis(100));
1519         gkoPool.setNumTestsPerEvictionRun(1);
1520         // Introduce latency between when evictor starts looking at an instance and when
1521         // it decides to destroy it
1522         gkoPool.setEvictionPolicy(new SlowEvictionPolicy<>(1000));
1523         // Borrow an instance
1524         final String val = gkoPool.borrowObject("foo");
1525         // Add another idle one
1526         gkoPool.addObject("foo");
1527         // Sleep long enough so idle one is eligible for eviction
1528         Thread.sleep(1000);
1529         // Start evictor and race with clearOldest
1530         gkoPool.setTimeBetweenEvictionRuns(Duration.ofMillis(10));
1531         // Wait for evictor to start
1532         Thread.sleep(100);
1533         gkoPool.clearOldest();
1534         // Wait for slow evictor to complete
1535         Thread.sleep(1500);
1536         // See if we get NPE on return (POOL-326)
1537         gkoPool.returnObject("foo", val);
1538     }
1539 
1540     /**
1541      * Verifies that the evictor visits objects in expected order and frequency.
1542      *
1543      * @throws Exception May occur in some failure modes
1544      */
1545     @Test
1546     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1547     void testEvictorVisiting() throws Exception {
1548         checkEvictorVisiting(true);
1549         checkEvictorVisiting(false);
1550     }
1551 
1552     @Test
1553     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1554     void testExceptionInValidationDuringEviction() throws Exception {
1555         gkoPool.setMaxIdlePerKey(1);
1556         gkoPool.setMinEvictableIdleTime(Duration.ZERO);
1557         gkoPool.setTestWhileIdle(true);
1558         final String obj = gkoPool.borrowObject("one");
1559         gkoPool.returnObject("one", obj);
1560         simpleFactory.setThrowExceptionOnValidate(true);
1561         assertThrows(RuntimeException.class, gkoPool::evict);
1562         assertEquals(0, gkoPool.getNumActive());
1563         assertEquals(0, gkoPool.getNumIdle());
1564     }
1565 
1566     @Test
1567     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1568     void testExceptionOnActivateDuringBorrow() throws Exception {
1569         final String obj1 = gkoPool.borrowObject("one");
1570         final String obj2 = gkoPool.borrowObject("one");
1571         gkoPool.returnObject("one", obj1);
1572         gkoPool.returnObject("one", obj2);
1573         simpleFactory.setThrowExceptionOnActivate(true);
1574         simpleFactory.setEvenValid(false);
1575         // Activation will now throw every other time
1576         // First attempt throws, but loop continues and second succeeds
1577         final String obj = gkoPool.borrowObject("one");
1578         assertEquals(1, gkoPool.getNumActive("one"));
1579         assertEquals(0, gkoPool.getNumIdle("one"));
1580         assertEquals(1, gkoPool.getNumActive());
1581         assertEquals(0, gkoPool.getNumIdle());
1582         gkoPool.returnObject("one", obj);
1583         simpleFactory.setValid(false);
1584         // Validation will now fail on activation when borrowObject returns
1585         // an idle instance, and then when attempting to create a new instance
1586         assertThrows(NoSuchElementException.class, () -> gkoPool.borrowObject("one"));
1587         assertEquals(0, gkoPool.getNumActive("one"));
1588         assertEquals(0, gkoPool.getNumIdle("one"));
1589         assertEquals(0, gkoPool.getNumActive());
1590         assertEquals(0, gkoPool.getNumIdle());
1591     }
1592 
1593     @Test
1594     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1595     void testExceptionOnDestroyDuringBorrow() throws Exception {
1596         simpleFactory.setThrowExceptionOnDestroy(true);
1597         simpleFactory.setValidationEnabled(true);
1598         gkoPool.setTestOnBorrow(true);
1599         gkoPool.borrowObject("one");
1600         simpleFactory.setValid(false); // Make validation fail on next borrow attempt
1601         assertThrows(NoSuchElementException.class, () -> gkoPool.borrowObject("one"));
1602         assertEquals(1, gkoPool.getNumActive("one"));
1603         assertEquals(0, gkoPool.getNumIdle("one"));
1604         assertEquals(1, gkoPool.getNumActive());
1605         assertEquals(0, gkoPool.getNumIdle());
1606     }
1607 
1608     @Test
1609     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1610     void testExceptionOnDestroyDuringReturn() throws Exception {
1611         simpleFactory.setThrowExceptionOnDestroy(true);
1612         simpleFactory.setValidationEnabled(true);
1613         gkoPool.setTestOnReturn(true);
1614         final String obj1 = gkoPool.borrowObject("one");
1615         gkoPool.borrowObject("one");
1616         simpleFactory.setValid(false); // Make validation fail
1617         gkoPool.returnObject("one", obj1);
1618         assertEquals(1, gkoPool.getNumActive("one"));
1619         assertEquals(0, gkoPool.getNumIdle("one"));
1620         assertEquals(1, gkoPool.getNumActive());
1621         assertEquals(0, gkoPool.getNumIdle());
1622     }
1623 
1624     @Test
1625     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1626     void testExceptionOnPassivateDuringReturn() throws Exception {
1627         final String obj = gkoPool.borrowObject("one");
1628         simpleFactory.setThrowExceptionOnPassivate(true);
1629         gkoPool.returnObject("one", obj);
1630         assertEquals(0, gkoPool.getNumIdle());
1631         gkoPool.close();
1632     }
1633 
1634     @Test
1635     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1636     void testFIFO() throws Exception {
1637         gkoPool.setLifo(false);
1638         final String key = "key";
1639         gkoPool.addObject(key); // "key0"
1640         gkoPool.addObject(key); // "key1"
1641         gkoPool.addObject(key); // "key2"
1642         assertEquals("key0", gkoPool.borrowObject(key), "Oldest");
1643         assertEquals("key1", gkoPool.borrowObject(key), "Middle");
1644         assertEquals("key2", gkoPool.borrowObject(key), "Youngest");
1645         final String s = gkoPool.borrowObject(key);
1646         assertEquals("key3", s, "new-3");
1647         gkoPool.returnObject(key, s);
1648         assertEquals(s, gkoPool.borrowObject(key), "returned");
1649         assertEquals("key4", gkoPool.borrowObject(key), "new-4");
1650     }
1651 
1652     @Test
1653     @Timeout(value = 60, unit = TimeUnit.SECONDS)
1654     void testGetKeys() throws Exception {
1655         gkoPool.addObject("one");
1656         assertEquals(1, gkoPool.getKeys().size());
1657         gkoPool.addObject("two");
1658         assertEquals(2, gkoPool.getKeys().size());
1659         gkoPool.clear("one");
1660         assertEquals(1, gkoPool.getKeys().size());
1661         assertEquals("two", gkoPool.getKeys().get(0));
1662         gkoPool.clear();
1663     }
1664 
1665     @Test
1666     void testGetStatsString() {
1667         assertNotNull(gkoPool.getStatsString());
1668     }
1669 
1670     /**
1671      * Verify that threads waiting on a depleted pool get served when a checked out object is invalidated.
1672      *
1673      * JIRA: POOL-240
1674      *
1675      * @throws InterruptedException Custom exception
1676      * @throws Exception            May occur in some failure modes
1677      */
1678     @Test
1679     void testInvalidateFreesCapacity() throws Exception {
1680         final SimpleFactory<String> factory = new SimpleFactory<>();
1681         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(factory)) {
1682             pool.setMaxTotalPerKey(2);
1683             pool.setMaxWaitMillis(500);
1684             // Borrow an instance and hold if for 5 seconds
1685             final WaitingTestThread thread1 = new WaitingTestThread(pool, "one", 5000);
1686             thread1.start();
1687             // Borrow another instance
1688             final String obj = pool.borrowObject("one");
1689             // Launch another thread - will block, but fail in 500 ms
1690             final WaitingTestThread thread2 = new WaitingTestThread(pool, "one", 100);
1691             thread2.start();
1692             // Invalidate the object borrowed by this thread - should allow thread2 to create
1693             Thread.sleep(20);
1694             pool.invalidateObject("one", obj);
1695             Thread.sleep(600); // Wait for thread2 to timeout
1696             if (thread2.thrown != null) {
1697                 fail(thread2.thrown.toString());
1698             }
1699         }
1700     }
1701 
1702     @Test
1703     void testInvalidateFreesCapacityForOtherKeys() throws Exception {
1704         gkoPool.setMaxTotal(1);
1705         gkoPool.setMaxWait(Duration.ofMillis(500));
1706         final Thread borrower = new Thread(new SimpleTestThread<>(gkoPool, "two"));
1707         final String obj = gkoPool.borrowObject("one");
1708         borrower.start(); // Will block
1709         Thread.sleep(100); // Make sure borrower has started
1710         gkoPool.invalidateObject("one", obj); // Should free capacity to serve the other key
1711         Thread.sleep(20); // Should have been served by now
1712         assertFalse(borrower.isAlive());
1713     }
1714 
1715     @Test
1716     void testInvalidateMissingKey() throws Exception {
1717         assertThrows(IllegalStateException.class, () -> gkoPool.invalidateObject("UnknownKey", "Ignored"));
1718     }
1719 
1720     @ParameterizedTest
1721     @EnumSource(DestroyMode.class)
1722     void testInvalidateMissingKeyForDestroyMode(final DestroyMode destroyMode) throws Exception {
1723         assertThrows(IllegalStateException.class, () -> gkoPool.invalidateObject("UnknownKey", "Ignored", destroyMode));
1724     }
1725 
1726     /**
1727      * Verify that threads blocked waiting on a depleted pool get served when a checked out instance is invalidated.
1728      *
1729      * JIRA: POOL-240
1730      *
1731      * @throws Exception May occur in some failure modes
1732      */
1733     @Test
1734     void testInvalidateWaiting() throws Exception {
1735         final GenericKeyedObjectPoolConfig<Object> config = new GenericKeyedObjectPoolConfig<>();
1736         config.setMaxTotal(2);
1737         config.setBlockWhenExhausted(true);
1738         config.setMinIdlePerKey(0);
1739         config.setMaxWait(Duration.ofMillis(-1));
1740         config.setNumTestsPerEvictionRun(Integer.MAX_VALUE); // always test all idle objects
1741         config.setTestOnBorrow(true);
1742         config.setTestOnReturn(false);
1743         config.setTestWhileIdle(true);
1744         config.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
1745         try (GenericKeyedObjectPool<Integer, Object> pool = new GenericKeyedObjectPool<>(new ObjectFactory(), config)) {
1746             // Allocate both objects with this thread
1747             pool.borrowObject(Integer.valueOf(1)); // object1
1748             final Object object2 = pool.borrowObject(Integer.valueOf(1));
1749             // Cause a thread to block waiting for an object
1750             final ExecutorService executorService = Executors.newSingleThreadExecutor(new DaemonThreadFactory());
1751             final Semaphore signal = new Semaphore(0);
1752             final Future<Exception> result = executorService.submit(() -> {
1753                 try {
1754                     signal.release();
1755                     final Object object3 = pool.borrowObject(Integer.valueOf(1));
1756                     pool.returnObject(Integer.valueOf(1), object3);
1757                     signal.release();
1758                 } catch (final Exception e1) {
1759                     return e1;
1760                 } catch (final Throwable e2) {
1761                     return new Exception(e2);
1762                 }
1763                 return null;
1764             });
1765             // Wait for the thread to start
1766             assertTrue(signal.tryAcquire(5, TimeUnit.SECONDS));
1767             // Attempt to ensure that test thread is blocked waiting for an object
1768             Thread.sleep(500);
1769             pool.invalidateObject(Integer.valueOf(1), object2);
1770             assertTrue(signal.tryAcquire(2, TimeUnit.SECONDS), "Call to invalidateObject did not unblock pool waiters.");
1771             if (result.get() != null) {
1772                 throw new AssertionError(result.get());
1773             }
1774         }
1775     }
1776 
1777     /**
1778      * Ensure the pool is registered.
1779      */
1780     @Test
1781     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1782     void testJmxRegistration() {
1783         final ObjectName oname = gkoPool.getJmxName();
1784         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
1785         final Set<ObjectName> result = mbs.queryNames(oname, null);
1786         assertEquals(1, result.size());
1787     }
1788 
1789     @Test
1790     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1791     void testLIFO() throws Exception {
1792         gkoPool.setLifo(true);
1793         final String key = "key";
1794         gkoPool.addObject(key); // "key0"
1795         gkoPool.addObject(key); // "key1"
1796         gkoPool.addObject(key); // "key2"
1797         assertEquals("key2", gkoPool.borrowObject(key), "Youngest");
1798         assertEquals("key1", gkoPool.borrowObject(key), "Middle");
1799         assertEquals("key0", gkoPool.borrowObject(key), "Oldest");
1800         final String s = gkoPool.borrowObject(key);
1801         assertEquals("key3", s, "new-3");
1802         gkoPool.returnObject(key, s);
1803         assertEquals(s, gkoPool.borrowObject(key), "returned");
1804         assertEquals("key4", gkoPool.borrowObject(key), "new-4");
1805     }
1806 
1807     /**
1808      * Verifies that threads that get parked waiting for keys not in use when the pool is at maxTotal eventually get served.
1809      *
1810      * @throws Exception May occur in some failure modes
1811      */
1812     @Test
1813     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1814     void testLivenessPerKey() throws Exception {
1815         gkoPool.setMaxIdlePerKey(3);
1816         gkoPool.setMaxTotal(3);
1817         gkoPool.setMaxTotalPerKey(3);
1818         gkoPool.setMaxWaitMillis(3000); // Really a timeout for the test
1819         // Check out and briefly hold 3 "1"s
1820         final WaitingTestThread t1 = new WaitingTestThread(gkoPool, "1", 100);
1821         final WaitingTestThread t2 = new WaitingTestThread(gkoPool, "1", 100);
1822         final WaitingTestThread t3 = new WaitingTestThread(gkoPool, "1", 100);
1823         t1.start();
1824         t2.start();
1825         t3.start();
1826         // Try to get a "2" while all capacity is in use.
1827         // Thread will park waiting on empty queue. Verify it gets served.
1828         gkoPool.borrowObject("2");
1829     }
1830 
1831     /**
1832      * Verify that factory exceptions creating objects do not corrupt per key create count.
1833      *
1834      * JIRA: POOL-243
1835      *
1836      * @throws Exception May occur in some failure modes
1837      */
1838     @Test
1839     void testMakeObjectException() throws Exception {
1840         final SimpleFactory<String> factory = new SimpleFactory<>();
1841         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(factory)) {
1842             pool.setMaxTotalPerKey(1);
1843             pool.setBlockWhenExhausted(false);
1844             factory.exceptionOnCreate = true;
1845             assertThrows(Exception.class, () -> pool.borrowObject("One"));
1846             factory.exceptionOnCreate = false;
1847             pool.borrowObject("One");
1848         }
1849     }
1850 
1851     /**
1852      * Test case for POOL-180.
1853      */
1854     @Test
1855     @Timeout(value = 200000, unit = TimeUnit.MILLISECONDS)
1856     void testMaxActivePerKeyExceeded() {
1857         final WaiterFactory<String> waiterFactory = new WaiterFactory<>(0, 20, 0, 0, 0, 0, 8, 5, 0);
1858         // TODO Fix this. Can't use local pool since runTestThreads uses the
1859         // protected pool field
1860         try (GenericKeyedObjectPool<String, Waiter> waiterPool = new GenericKeyedObjectPool<>(waiterFactory)) {
1861             waiterPool.setMaxTotalPerKey(5);
1862             waiterPool.setMaxTotal(8);
1863             waiterPool.setTestOnBorrow(true);
1864             waiterPool.setMaxIdlePerKey(5);
1865             waiterPool.setMaxWaitMillis(-1);
1866             runTestThreads(20, 300, 250, waiterPool);
1867         }
1868     }
1869 
1870     @Test
1871     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1872     void testMaxIdle() throws Exception {
1873         gkoPool.setMaxTotalPerKey(100);
1874         gkoPool.setMaxIdlePerKey(8);
1875         final String[] active = new String[100];
1876         for (int i = 0; i < 100; i++) {
1877             active[i] = gkoPool.borrowObject("");
1878         }
1879         assertEquals(100, gkoPool.getNumActive(""));
1880         assertEquals(0, gkoPool.getNumIdle(""));
1881         for (int i = 0; i < 100; i++) {
1882             gkoPool.returnObject("", active[i]);
1883             assertEquals(99 - i, gkoPool.getNumActive(""));
1884             assertEquals(i < 8 ? i + 1 : 8, gkoPool.getNumIdle(""));
1885         }
1886         for (int i = 0; i < 100; i++) {
1887             active[i] = gkoPool.borrowObject("a");
1888         }
1889         assertEquals(100, gkoPool.getNumActive("a"));
1890         assertEquals(0, gkoPool.getNumIdle("a"));
1891         for (int i = 0; i < 100; i++) {
1892             gkoPool.returnObject("a", active[i]);
1893             assertEquals(99 - i, gkoPool.getNumActive("a"));
1894             assertEquals(i < 8 ? i + 1 : 8, gkoPool.getNumIdle("a"));
1895         }
1896         // total number of idle instances is twice maxIdle
1897         assertEquals(16, gkoPool.getNumIdle());
1898         // Each pool is at the sup
1899         assertEquals(8, gkoPool.getNumIdle(""));
1900         assertEquals(8, gkoPool.getNumIdle("a"));
1901     }
1902 
1903     @Test
1904     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1905     void testMaxTotal() throws Exception {
1906         gkoPool.setMaxTotalPerKey(2);
1907         gkoPool.setMaxTotal(3);
1908         gkoPool.setBlockWhenExhausted(false);
1909         final String o1 = gkoPool.borrowObject("a");
1910         assertNotNull(o1);
1911         final String o2 = gkoPool.borrowObject("a");
1912         assertNotNull(o2);
1913         final String o3 = gkoPool.borrowObject("b");
1914         assertNotNull(o3);
1915         assertThrows(NoSuchElementException.class, () -> gkoPool.borrowObject("c"));
1916         assertEquals(0, gkoPool.getNumIdle());
1917         gkoPool.returnObject("b", o3);
1918         assertEquals(1, gkoPool.getNumIdle());
1919         assertEquals(1, gkoPool.getNumIdle("b"));
1920         final Object o4 = gkoPool.borrowObject("b");
1921         assertNotNull(o4);
1922         assertEquals(0, gkoPool.getNumIdle());
1923         assertEquals(0, gkoPool.getNumIdle("b"));
1924         gkoPool.setMaxTotal(4);
1925         final Object o5 = gkoPool.borrowObject("b");
1926         assertNotNull(o5);
1927         assertEquals(2, gkoPool.getNumActive("a"));
1928         assertEquals(2, gkoPool.getNumActive("b"));
1929         assertEquals(gkoPool.getMaxTotal(), gkoPool.getNumActive("b") + gkoPool.getNumActive("b"));
1930         assertEquals(gkoPool.getNumActive(), gkoPool.getMaxTotal());
1931     }
1932 
1933     /**
1934      * Verifies that maxTotal is not exceeded when factory destroyObject has high latency, testOnReturn is set and there is high incidence of validation
1935      * failures.
1936      */
1937     @Test
1938     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1939     void testMaxTotalInvariant() {
1940         final int maxTotal = 15;
1941         simpleFactory.setEvenValid(false); // Every other validation fails
1942         simpleFactory.setDestroyLatency(100); // Destroy takes 100 ms
1943         simpleFactory.setMaxTotalPerKey(maxTotal); // (makes - destroys) bound
1944         simpleFactory.setValidationEnabled(true);
1945         gkoPool.setMaxTotal(maxTotal);
1946         gkoPool.setMaxIdlePerKey(-1);
1947         gkoPool.setTestOnReturn(true);
1948         gkoPool.setMaxWaitMillis(10000L);
1949         runTestThreads(5, 10, 50, gkoPool);
1950     }
1951 
1952     @Test
1953     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1954     void testMaxTotalLRU() throws Exception {
1955         gkoPool.setMaxTotalPerKey(2);
1956         gkoPool.setMaxTotal(3);
1957         final String o1 = gkoPool.borrowObject("a");
1958         assertNotNull(o1);
1959         gkoPool.returnObject("a", o1);
1960         Thread.sleep(25);
1961         final String o2 = gkoPool.borrowObject("b");
1962         assertNotNull(o2);
1963         gkoPool.returnObject("b", o2);
1964         Thread.sleep(25);
1965         final String o3 = gkoPool.borrowObject("c");
1966         assertNotNull(o3);
1967         gkoPool.returnObject("c", o3);
1968         Thread.sleep(25);
1969         final String o4 = gkoPool.borrowObject("a");
1970         assertNotNull(o4);
1971         gkoPool.returnObject("a", o4);
1972         Thread.sleep(25);
1973         assertSame(o1, o4);
1974         // this should cause b to be bumped out of the pool
1975         final String o5 = gkoPool.borrowObject("d");
1976         assertNotNull(o5);
1977         gkoPool.returnObject("d", o5);
1978         Thread.sleep(25);
1979         // now re-request b, we should get a different object because it should
1980         // have been expelled from pool (was oldest because a was requested after b)
1981         final String o6 = gkoPool.borrowObject("b");
1982         assertNotNull(o6);
1983         gkoPool.returnObject("b", o6);
1984         assertNotSame(o1, o6);
1985         assertNotSame(o2, o6);
1986         // second a is still in there
1987         final String o7 = gkoPool.borrowObject("a");
1988         assertNotNull(o7);
1989         gkoPool.returnObject("a", o7);
1990         assertSame(o4, o7);
1991     }
1992 
1993     @Test
1994     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
1995     void testMaxTotalPerKey() throws Exception {
1996         gkoPool.setMaxTotalPerKey(3);
1997         gkoPool.setBlockWhenExhausted(false);
1998         gkoPool.borrowObject("");
1999         gkoPool.borrowObject("");
2000         gkoPool.borrowObject("");
2001         assertThrows(NoSuchElementException.class, () -> gkoPool.borrowObject(""));
2002     }
2003 
2004     @Test
2005     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2006     void testMaxTotalPerKeyZero() {
2007         gkoPool.setMaxTotalPerKey(0);
2008         gkoPool.setBlockWhenExhausted(false);
2009         assertThrows(NoSuchElementException.class, () -> gkoPool.borrowObject("a"));
2010     }
2011 
2012     /**
2013      * Verifies that if a borrow of a new key is blocked because maxTotal has been reached, that borrow continues once another object is returned.
2014      *
2015      * JIRA: POOL-310
2016      */
2017     @Test
2018     void testMaxTotalWithThreads() throws Exception {
2019         gkoPool.setMaxTotalPerKey(2);
2020         gkoPool.setMaxTotal(1);
2021         final int holdTime = 2000;
2022         final TestThread<String> testA = new TestThread<>(gkoPool, 1, 0, holdTime, false, null, "a");
2023         final TestThread<String> testB = new TestThread<>(gkoPool, 1, 0, holdTime, false, null, "b");
2024         final Thread threadA = new Thread(testA);
2025         final Thread threadB = new Thread(testB);
2026         threadA.start();
2027         threadB.start();
2028         Thread.sleep(holdTime * 2);
2029         // Both threads should be complete now.
2030         boolean threadRunning = true;
2031         int count = 0;
2032         while (threadRunning && count < 15) {
2033             threadRunning = threadA.isAlive();
2034             threadRunning = threadB.isAlive();
2035             Thread.sleep(200);
2036             count++;
2037         }
2038         assertFalse(threadA.isAlive());
2039         assertFalse(threadB.isAlive());
2040         assertFalse(testA.failed);
2041         assertFalse(testB.failed);
2042     }
2043 
2044     @Test
2045     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2046     void testMaxTotalZero() {
2047         gkoPool.setMaxTotal(0);
2048         gkoPool.setBlockWhenExhausted(false);
2049         assertThrows(NoSuchElementException.class, () -> gkoPool.borrowObject("a"));
2050     }
2051 
2052     /*
2053      * Test multi-threaded pool access. Multiple keys, multiple threads, but maxActive only allows half the threads to succeed.
2054      *
2055      * This test was prompted by Continuum build failures in the Commons DBCP test case: TestSharedPoolDataSource.testMultipleThreads2() Let's see if the this
2056      * fails on Continuum too!
2057      */
2058     @Test
2059     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2060     void testMaxWaitMultiThreaded() throws Exception {
2061         final long maxWait = 500; // wait for connection
2062         final long holdTime = 4 * maxWait; // how long to hold connection
2063         final int keyCount = 4; // number of different keys
2064         final int threadsPerKey = 5; // number of threads to grab the key initially
2065         gkoPool.setBlockWhenExhausted(true);
2066         gkoPool.setMaxWaitMillis(maxWait);
2067         gkoPool.setMaxTotalPerKey(threadsPerKey);
2068         // Create enough threads so half the threads will have to wait
2069         final WaitingTestThread[] wtt = new WaitingTestThread[keyCount * threadsPerKey * 2];
2070         for (int i = 0; i < wtt.length; i++) {
2071             wtt[i] = new WaitingTestThread(gkoPool, Integer.toString(i % keyCount), holdTime);
2072         }
2073         final long originMillis = System.currentTimeMillis() - 1000;
2074         for (final WaitingTestThread element : wtt) {
2075             element.start();
2076         }
2077         int failed = 0;
2078         for (final WaitingTestThread element : wtt) {
2079             element.join();
2080             if (element.thrown != null) {
2081                 failed++;
2082             }
2083         }
2084         if (DISPLAY_THREAD_DETAILS || wtt.length / 2 != failed) {
2085             System.out.println("MaxWait: " + maxWait + " HoldTime: " + holdTime + " KeyCount: " + keyCount + " MaxActive: " + threadsPerKey + " Threads: "
2086                     + wtt.length + " Failed: " + failed);
2087             for (final WaitingTestThread wt : wtt) {
2088                 System.out.println("Preborrow: " + (wt.preBorrowMillis - originMillis) + " Postborrow: "
2089                         + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - originMillis : -1) + " BorrowTime: "
2090                         + (wt.postBorrowMillis != 0 ? wt.postBorrowMillis - wt.preBorrowMillis : -1) + " PostReturn: "
2091                         + (wt.postReturnMillis != 0 ? wt.postReturnMillis - originMillis : -1) + " Ended: " + (wt.endedMillis - originMillis) + " Key: "
2092                         + wt.key + " ObjId: " + wt.objectId);
2093             }
2094         }
2095         assertEquals(wtt.length / 2, failed, "Expected half the threads to fail");
2096     }
2097 
2098 
2099     /**
2100      * JIRA: POOL-420 (clone of POOL-418 for GKOP)
2101      *
2102      * Test to make sure that a client thread that triggers a create that fails does
2103      * not wait longer than the maxWait time.
2104      *
2105      * Bug was that the time spent waiting for the create to complete was not being
2106      * counted against the maxWait time.
2107      */
2108     @Test
2109     void testMaxWaitTimeOutOnTime() throws Exception {
2110         final Duration maxWaitDuration = Duration.ofSeconds(1);
2111         final SimpleFactory<String> factory = new SimpleFactory<>();
2112         factory.setMakeLatency(500);
2113         factory.setValidationEnabled(true); // turn on factory-level validation
2114         factory.setValid(false); // make validation fail uniformly
2115         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(factory)) {
2116             pool.setBlockWhenExhausted(true);
2117             pool.setMaxWait(maxWaitDuration);
2118             pool.setMaxTotalPerKey(1);
2119             pool.setMaxTotal(1);
2120             pool.setTestOnCreate(true);
2121             final Instant startTime = Instant.now();
2122             // Try to borrow an object. Validation will fail.
2123             // Then we will wait on the pool.
2124             try {
2125                 pool.borrowObject("a");
2126             } catch (final NoSuchElementException ex) {
2127                 // expected
2128             }
2129             // Should have timed out after 1000 ms from the start time
2130             final Duration duration = Duration.between(startTime, Instant.now());
2131             final long clockGranularityMillis = 20;
2132             assertTrue(duration.toMillis() < maxWaitDuration.toMillis() + clockGranularityMillis, // allow for clock granularity.
2133                     "Thread A should have timed out after " + maxWaitDuration.toMillis() + " ms, but took " + duration.toMillis() + " ms");
2134         }
2135     }
2136 
2137     @Test
2138     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2139     void testMinIdle() throws Exception {
2140         gkoPool.setMaxIdlePerKey(500);
2141         gkoPool.setMinIdlePerKey(5);
2142         gkoPool.setMaxTotalPerKey(10);
2143         gkoPool.setNumTestsPerEvictionRun(0);
2144         gkoPool.setMinEvictableIdleTime(Duration.ofMillis(50));
2145         gkoPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
2146         gkoPool.setTestWhileIdle(true);
2147         // Generate a random key
2148         final String key = "A";
2149         gkoPool.preparePool(key);
2150         Waiter.sleepQuietly(150L);
2151         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2152         final String[] active = new String[5];
2153         active[0] = gkoPool.borrowObject(key);
2154         Waiter.sleepQuietly(150L);
2155         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2156         for (int i = 1; i < 5; i++) {
2157             active[i] = gkoPool.borrowObject(key);
2158         }
2159         Waiter.sleepQuietly(150L);
2160         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2161         for (int i = 0; i < 5; i++) {
2162             gkoPool.returnObject(key, active[i]);
2163         }
2164         Waiter.sleepQuietly(150L);
2165         assertEquals(10, gkoPool.getNumIdle(), "Should be 10 idle, found " + gkoPool.getNumIdle());
2166     }
2167 
2168     @Test
2169     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2170     void testMinIdleMaxTotalPerKey() throws Exception {
2171         gkoPool.setMaxIdlePerKey(500);
2172         gkoPool.setMinIdlePerKey(5);
2173         gkoPool.setMaxTotalPerKey(10);
2174         gkoPool.setNumTestsPerEvictionRun(0);
2175         gkoPool.setMinEvictableIdleTime(Duration.ofMillis(50));
2176         gkoPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
2177         gkoPool.setTestWhileIdle(true);
2178         final String key = "A";
2179         gkoPool.preparePool(key);
2180         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2181         Waiter.sleepQuietly(150L);
2182         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2183         final String[] active = new String[10];
2184         Waiter.sleepQuietly(150L);
2185         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2186         for (int i = 0; i < 5; i++) {
2187             active[i] = gkoPool.borrowObject(key);
2188         }
2189         Waiter.sleepQuietly(150L);
2190         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2191         for (int i = 0; i < 5; i++) {
2192             gkoPool.returnObject(key, active[i]);
2193         }
2194         Waiter.sleepQuietly(150L);
2195         assertEquals(10, gkoPool.getNumIdle(), "Should be 10 idle, found " + gkoPool.getNumIdle());
2196         for (int i = 0; i < 10; i++) {
2197             active[i] = gkoPool.borrowObject(key);
2198         }
2199         Waiter.sleepQuietly(150L);
2200         assertEquals(0, gkoPool.getNumIdle(), "Should be 0 idle, found " + gkoPool.getNumIdle());
2201         for (int i = 0; i < 10; i++) {
2202             gkoPool.returnObject(key, active[i]);
2203         }
2204         Waiter.sleepQuietly(150L);
2205         assertEquals(10, gkoPool.getNumIdle(), "Should be 10 idle, found " + gkoPool.getNumIdle());
2206     }
2207 
2208     @Test
2209     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2210     void testMinIdleNoPreparePool() throws Exception {
2211         gkoPool.setMaxIdlePerKey(500);
2212         gkoPool.setMinIdlePerKey(5);
2213         gkoPool.setMaxTotalPerKey(10);
2214         gkoPool.setNumTestsPerEvictionRun(0);
2215         gkoPool.setMinEvictableIdleTime(Duration.ofMillis(50));
2216         gkoPool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
2217         gkoPool.setTestWhileIdle(true);
2218         // Generate a random key
2219         final String key = "A";
2220         Waiter.sleepQuietly(150L);
2221         assertEquals(0, gkoPool.getNumIdle(), "Should be 0 idle, found " + gkoPool.getNumIdle());
2222         final Object active = gkoPool.borrowObject(key);
2223         assertNotNull(active);
2224         Waiter.sleepQuietly(150L);
2225         assertEquals(5, gkoPool.getNumIdle(), "Should be 5 idle, found " + gkoPool.getNumIdle());
2226     }
2227 
2228     /**
2229      * Verifies that returning an object twice (without borrow in between) causes ISE but does not re-validate or re-passivate the instance.
2230      *
2231      * JIRA: POOL-285
2232      */
2233     @Test
2234     void testMultipleReturn() throws Exception {
2235         final WaiterFactory<String> factory = new WaiterFactory<>(0, 0, 0, 0, 0, 0);
2236         try (GenericKeyedObjectPool<String, Waiter> pool = new GenericKeyedObjectPool<>(factory)) {
2237             pool.setTestOnReturn(true);
2238             final Waiter waiter = pool.borrowObject("a");
2239             pool.returnObject("a", waiter);
2240             assertEquals(1, waiter.getValidationCount());
2241             assertEquals(1, waiter.getPassivationCount());
2242             try {
2243                 pool.returnObject("a", waiter);
2244                 fail("Expecting IllegalStateException from multiple return");
2245             } catch (final IllegalStateException ex) {
2246                 // Exception is expected, now check no repeat validation/passivation
2247                 assertEquals(1, waiter.getValidationCount());
2248                 assertEquals(1, waiter.getPassivationCount());
2249             }
2250         }
2251     }
2252 
2253     /**
2254      * Verifies that when a borrowed object is mutated in a way that does not preserve equality and hash code, the pool can recognized it on return.
2255      *
2256      * JIRA: POOL-284
2257      */
2258     @Test
2259     void testMutable() throws Exception {
2260         final HashSetFactory factory = new HashSetFactory();
2261         try (GenericKeyedObjectPool<String, HashSet<String>> pool = new GenericKeyedObjectPool<>(factory, new GenericKeyedObjectPoolConfig<>())) {
2262             final HashSet<String> s1 = pool.borrowObject("a");
2263             final HashSet<String> s2 = pool.borrowObject("a");
2264             s1.add("One");
2265             s2.add("One");
2266             pool.returnObject("a", s1);
2267             pool.returnObject("a", s2);
2268         }
2269     }
2270 
2271     @Test
2272     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2273     void testNegativeMaxTotalPerKey() throws Exception {
2274         gkoPool.setMaxTotalPerKey(-1);
2275         gkoPool.setBlockWhenExhausted(false);
2276         final String obj = gkoPool.borrowObject("");
2277         assertEquals("0", obj);
2278         gkoPool.returnObject("", obj);
2279     }
2280 
2281     /**
2282      * Verify that when a factory returns a null object, pool methods throw NPE.
2283      */
2284     @Test
2285     @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS)
2286     void testNPEOnFactoryNull() {
2287         // @formatter:off
2288         final DisconnectingWaiterFactory<String> factory = new DisconnectingWaiterFactory<>(
2289             Suppliers.nul(),  // Override default to always return null from makeObject
2290             DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_LIFECYCLE_ACTION,
2291             DisconnectingWaiterFactory.DEFAULT_DISCONNECTED_VALIDATION_ACTION
2292         );
2293         // @formatter:on
2294         try (GenericKeyedObjectPool<String, Waiter> pool = new GenericKeyedObjectPool<>(factory)) {
2295             final String key = "one";
2296             pool.setTestOnBorrow(true);
2297             pool.setMaxTotal(-1);
2298             pool.setMinIdlePerKey(1);
2299             // Disconnect the factory - will always return null in this state
2300             factory.disconnect();
2301             assertThrows(NullPointerException.class, () -> pool.borrowObject(key));
2302             assertThrows(NullPointerException.class, () -> pool.addObject(key));
2303             assertThrows(NullPointerException.class, pool::ensureMinIdle);
2304         }
2305     }
2306 
2307     @Test
2308     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2309     void testNumActiveNumIdle2() throws Exception {
2310         assertEquals(0, gkoPool.getNumActive());
2311         assertEquals(0, gkoPool.getNumIdle());
2312         assertEquals(0, gkoPool.getNumActive("A"));
2313         assertEquals(0, gkoPool.getNumIdle("A"));
2314         assertEquals(0, gkoPool.getNumActive("B"));
2315         assertEquals(0, gkoPool.getNumIdle("B"));
2316         final String objA0 = gkoPool.borrowObject("A");
2317         final String objB0 = gkoPool.borrowObject("B");
2318         assertEquals(2, gkoPool.getNumActive());
2319         assertEquals(0, gkoPool.getNumIdle());
2320         assertEquals(1, gkoPool.getNumActive("A"));
2321         assertEquals(0, gkoPool.getNumIdle("A"));
2322         assertEquals(1, gkoPool.getNumActive("B"));
2323         assertEquals(0, gkoPool.getNumIdle("B"));
2324         final String objA1 = gkoPool.borrowObject("A");
2325         final String objB1 = gkoPool.borrowObject("B");
2326         assertEquals(4, gkoPool.getNumActive());
2327         assertEquals(0, gkoPool.getNumIdle());
2328         assertEquals(2, gkoPool.getNumActive("A"));
2329         assertEquals(0, gkoPool.getNumIdle("A"));
2330         assertEquals(2, gkoPool.getNumActive("B"));
2331         assertEquals(0, gkoPool.getNumIdle("B"));
2332         gkoPool.returnObject("A", objA0);
2333         gkoPool.returnObject("B", objB0);
2334         assertEquals(2, gkoPool.getNumActive());
2335         assertEquals(2, gkoPool.getNumIdle());
2336         assertEquals(1, gkoPool.getNumActive("A"));
2337         assertEquals(1, gkoPool.getNumIdle("A"));
2338         assertEquals(1, gkoPool.getNumActive("B"));
2339         assertEquals(1, gkoPool.getNumIdle("B"));
2340         gkoPool.returnObject("A", objA1);
2341         gkoPool.returnObject("B", objB1);
2342         assertEquals(0, gkoPool.getNumActive());
2343         assertEquals(4, gkoPool.getNumIdle());
2344         assertEquals(0, gkoPool.getNumActive("A"));
2345         assertEquals(2, gkoPool.getNumIdle("A"));
2346         assertEquals(0, gkoPool.getNumActive("B"));
2347         assertEquals(2, gkoPool.getNumIdle("B"));
2348     }
2349 
2350     @Test
2351     void testReturnObjectThrowsIllegalStateException() {
2352         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(new SimpleFactory<>())) {
2353             assertThrows(IllegalStateException.class, () -> pool.returnObject("Foo", "Bar"));
2354         }
2355     }
2356 
2357     @Test
2358     void testReturnObjectWithBlockWhenExhausted() throws Exception {
2359         gkoPool.setBlockWhenExhausted(true);
2360         gkoPool.setMaxTotal(1);
2361         // Test return object with no take waiters
2362         final String obj = gkoPool.borrowObject("0");
2363         gkoPool.returnObject("0", obj);
2364         // Test return object with a take waiter
2365         final TestThread<String> testA = new TestThread<>(gkoPool, 1, 0, 500, false, null, "0");
2366         final TestThread<String> testB = new TestThread<>(gkoPool, 1, 0, 0, false, null, "1");
2367         final Thread threadA = new Thread(testA);
2368         final Thread threadB = new Thread(testB);
2369         threadA.start();
2370         threadB.start();
2371         threadA.join();
2372         threadB.join();
2373     }
2374 
2375     @Test
2376     void testReturnObjectWithoutBlockWhenExhausted() throws Exception {
2377         gkoPool.setBlockWhenExhausted(false);
2378         // Test return object with no take waiters
2379         final String obj = gkoPool.borrowObject("0");
2380         gkoPool.returnObject("0", obj);
2381     }
2382 
2383     /**
2384      * JIRA: POOL-287
2385      *
2386      * Verify that when an attempt is made to borrow an instance from the pool while the evictor is visiting it, there is no capacity leak.
2387      *
2388      * Test creates the scenario described in POOL-287.
2389      */
2390     @Test
2391     void testReturnToHead() throws Exception {
2392         final SimpleFactory<String> factory = new SimpleFactory<>();
2393         factory.setValidateLatency(100);
2394         factory.setValid(true); // Validation always succeeds
2395         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(factory)) {
2396             pool.setMaxWaitMillis(1000);
2397             pool.setTestWhileIdle(true);
2398             pool.setMaxTotalPerKey(2);
2399             pool.setNumTestsPerEvictionRun(1);
2400             pool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
2401             // Load pool with two objects
2402             pool.addObject("one"); // call this o1
2403             pool.addObject("one"); // call this o2
2404             // Default is LIFO, so "one" pool is now [o2, o1] in offer order.
2405             // Evictor will visit in oldest-to-youngest order, so o1 then o2
2406             Thread.sleep(800); // Wait for first eviction run to complete
2407             // At this point, one eviction run should have completed, visiting o1
2408             // and eviction cursor should be pointed at o2, which is the next offered instance
2409             Thread.sleep(250); // Wait for evictor to start
2410             final String o1 = pool.borrowObject("one"); // o2 is under eviction, so this will return o1
2411             final String o2 = pool.borrowObject("one"); // Once validation completes, o2 should be offered
2412             pool.returnObject("one", o1);
2413             pool.returnObject("one", o2);
2414         }
2415     }
2416 
2417     /**
2418      * Verify that when reuseCapacityOnMaintenance is true, eviction triggers reuseCapacity
2419      * and when reuseCapacityOnReturn is false, returning an object does not trigger reuseCapacity.
2420      * JIRA: POOL-350
2421      */
2422     @Test
2423     @Timeout(value = 10_000, unit = TimeUnit.MILLISECONDS)
2424     void testReuseCapacityOnMaintenanceBehavior() throws Exception {
2425         gkoPool.setMaxTotalPerKey(2);
2426         gkoPool.setMaxTotal(4);
2427         gkoPool.setBlockWhenExhausted(true);
2428         gkoPool.setMaxWait(Duration.ofSeconds(5));
2429         gkoPool.setReuseCapacityOnReturn(false);
2430         gkoPool.setReuseCapacityOnMaintenance(true);
2431 
2432         // Create a waiter on key1
2433         final Thread waiter = new Thread(new SimpleTestThread<>(gkoPool, "key1"));
2434 
2435         // Exhaust capacity
2436         final String obj1 = gkoPool.borrowObject("key2");
2437         final String obj2 = gkoPool.borrowObject("key2");
2438         final String obj3 = gkoPool.borrowObject("key3");
2439         final String obj4 = gkoPool.borrowObject("key3");
2440 
2441         Thread.sleep(100);
2442 
2443         // Launch the waiter - it will be blocked
2444         waiter.start();
2445         Thread.sleep(100);
2446 
2447 
2448         // Return one object to free capacity
2449         gkoPool.returnObject("key2", obj1);
2450 
2451         // Even with capacity available, waiter should still be blocked
2452         // because reuseCapacityOnReturn is false
2453         Thread.sleep(100);
2454         assertTrue(waiter.isAlive());
2455 
2456         // Call evict - with reuseCapacityOnMaintenance=true, this should serve the waiter
2457         gkoPool.evict();
2458         Thread.sleep(100);
2459 
2460         // Waiter should have been served
2461         assertFalse(waiter.isAlive());
2462 
2463         // Clean up
2464         gkoPool.returnObject("key2", obj2);
2465         gkoPool.returnObject("key3", obj3);
2466         gkoPool.returnObject("key3", obj4);
2467     }
2468 
2469     @Test
2470     void testReuseCapacityOnMaintenanceConfig() throws Exception {
2471         // Test default value
2472         assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_MAINTENANCE, gkoPool.getReuseCapacityOnMaintenance());
2473         assertFalse(gkoPool.getReuseCapacityOnMaintenance());
2474 
2475         // Test setting via config object
2476         final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>();
2477         config.setReuseCapacityOnMaintenance(true);
2478         assertEquals(true, config.getReuseCapacityOnMaintenance());
2479 
2480         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) {
2481             assertEquals(true, pool.getReuseCapacityOnMaintenance());
2482         }
2483 
2484         // Test setter
2485         gkoPool.setReuseCapacityOnMaintenance(true);
2486         assertEquals(true, gkoPool.getReuseCapacityOnMaintenance());
2487 
2488         gkoPool.setReuseCapacityOnMaintenance(false);
2489         assertEquals(false, gkoPool.getReuseCapacityOnMaintenance());
2490     }
2491 
2492     @Test
2493     void testReuseCapacityOnReturnConfig() throws Exception {
2494         // Test default value
2495         assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_REUSE_CAPACITY_ON_RETURN, gkoPool.getReuseCapacityOnReturn());
2496         assertTrue(gkoPool.getReuseCapacityOnReturn());
2497 
2498         // Test setting via config object
2499         final GenericKeyedObjectPoolConfig<String> config = new GenericKeyedObjectPoolConfig<>();
2500         config.setReuseCapacityOnReturn(false);
2501         assertEquals(false, config.getReuseCapacityOnReturn());
2502 
2503         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(simpleFactory, config)) {
2504             assertEquals(false, pool.getReuseCapacityOnReturn());
2505         }
2506 
2507         // Test setter
2508         gkoPool.setReuseCapacityOnReturn(false);
2509         assertEquals(false, gkoPool.getReuseCapacityOnReturn());
2510 
2511         gkoPool.setReuseCapacityOnReturn(true);
2512         assertEquals(true, gkoPool.getReuseCapacityOnReturn());
2513     }
2514 
2515     @Test
2516     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2517     void testSettersAndGetters() {
2518         {
2519             gkoPool.setMaxTotalPerKey(123);
2520             assertEquals(123, gkoPool.getMaxTotalPerKey());
2521         }
2522         {
2523             gkoPool.setMaxIdlePerKey(12);
2524             assertEquals(12, gkoPool.getMaxIdlePerKey());
2525         }
2526         {
2527             gkoPool.setMaxWaitMillis(1234L);
2528             assertEquals(1234L, gkoPool.getMaxWaitMillis());
2529         }
2530         {
2531             gkoPool.setMinEvictableIdleTimeMillis(12345L);
2532             assertEquals(12345L, gkoPool.getMinEvictableIdleTimeMillis());
2533         }
2534         {
2535             gkoPool.setMinEvictableIdleTime(Duration.ofMillis(12345L));
2536             assertEquals(12345L, gkoPool.getMinEvictableIdleTime().toMillis());
2537         }
2538         {
2539             gkoPool.setMinEvictableIdleTime(Duration.ofMillis(12345L));
2540             assertEquals(12345L, gkoPool.getMinEvictableIdleDuration().toMillis());
2541         }
2542         {
2543             gkoPool.setNumTestsPerEvictionRun(11);
2544             assertEquals(11, gkoPool.getNumTestsPerEvictionRun());
2545         }
2546         {
2547             gkoPool.setTestOnBorrow(true);
2548             assertTrue(gkoPool.getTestOnBorrow());
2549             gkoPool.setTestOnBorrow(false);
2550             assertFalse(gkoPool.getTestOnBorrow());
2551         }
2552         {
2553             gkoPool.setTestOnReturn(true);
2554             assertTrue(gkoPool.getTestOnReturn());
2555             gkoPool.setTestOnReturn(false);
2556             assertFalse(gkoPool.getTestOnReturn());
2557         }
2558         {
2559             gkoPool.setTestWhileIdle(true);
2560             assertTrue(gkoPool.getTestWhileIdle());
2561             gkoPool.setTestWhileIdle(false);
2562             assertFalse(gkoPool.getTestWhileIdle());
2563         }
2564         {
2565             gkoPool.setTimeBetweenEvictionRunsMillis(11235L);
2566             assertEquals(11235L, gkoPool.getDurationBetweenEvictionRuns().toMillis());
2567             assertEquals(11235L, gkoPool.getTimeBetweenEvictionRuns().toMillis());
2568             assertEquals(11235L, gkoPool.getTimeBetweenEvictionRunsMillis());
2569         }
2570         {
2571             gkoPool.setTimeBetweenEvictionRuns(Duration.ofMillis(11235L));
2572             assertEquals(11235L, gkoPool.getDurationBetweenEvictionRuns().toMillis());
2573             assertEquals(11235L, gkoPool.getTimeBetweenEvictionRuns().toMillis());
2574             assertEquals(11235L, gkoPool.getTimeBetweenEvictionRunsMillis());
2575         }
2576         {
2577             gkoPool.setBlockWhenExhausted(true);
2578             assertTrue(gkoPool.getBlockWhenExhausted());
2579             gkoPool.setBlockWhenExhausted(false);
2580             assertFalse(gkoPool.getBlockWhenExhausted());
2581         }
2582     }
2583 
2584     @Test
2585     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2586     void testThreaded1() {
2587         gkoPool.setMaxTotalPerKey(15);
2588         gkoPool.setMaxIdlePerKey(15);
2589         gkoPool.setMaxWaitMillis(1000L);
2590         runTestThreads(20, 100, 50, gkoPool);
2591     }
2592 
2593     // Pool-361
2594     @Test
2595     void testValidateOnCreate() throws Exception {
2596         gkoPool.setTestOnCreate(true);
2597         simpleFactory.setValidationEnabled(true);
2598         gkoPool.addObject("one");
2599         assertEquals(1, simpleFactory.validateCounter);
2600     }
2601 
2602     @Test
2603     void testValidateOnCreateFailure() throws Exception {
2604         gkoPool.setTestOnCreate(true);
2605         gkoPool.setTestOnBorrow(false);
2606         gkoPool.setMaxTotal(2);
2607         simpleFactory.setValidationEnabled(true);
2608         simpleFactory.setValid(false);
2609         // Make sure failed validations do not leak capacity
2610         gkoPool.addObject("one");
2611         gkoPool.addObject("one");
2612         assertEquals(0, gkoPool.getNumIdle());
2613         assertEquals(0, gkoPool.getNumActive());
2614         simpleFactory.setValid(true);
2615         final String obj = gkoPool.borrowObject("one");
2616         assertNotNull(obj);
2617         gkoPool.addObject("one");
2618         // Should have one idle, one out now
2619         assertEquals(1, gkoPool.getNumIdle());
2620         assertEquals(1, gkoPool.getNumActive());
2621     }
2622 
2623     /**
2624      * Verify that threads waiting on a depleted pool get served when a returning object fails validation.
2625      *
2626      * JIRA: POOL-240
2627      *
2628      * @throws Exception May occur in some failure modes
2629      */
2630     @Test
2631     void testValidationFailureOnReturnFreesCapacity() throws Exception {
2632         final SimpleFactory<String> factory = new SimpleFactory<>();
2633         factory.setValid(false); // Validate will always fail
2634         factory.setValidationEnabled(true);
2635         try (GenericKeyedObjectPool<String, String> pool = new GenericKeyedObjectPool<>(factory)) {
2636             pool.setMaxTotalPerKey(2);
2637             pool.setMaxWaitMillis(1500);
2638             pool.setTestOnReturn(true);
2639             pool.setTestOnBorrow(false);
2640             // Borrow an instance and hold if for 5 seconds
2641             final WaitingTestThread thread1 = new WaitingTestThread(pool, "one", 5000);
2642             thread1.start();
2643             // Borrow another instance and return it after 500 ms (validation will fail)
2644             final WaitingTestThread thread2 = new WaitingTestThread(pool, "one", 500);
2645             thread2.start();
2646             Thread.sleep(50);
2647             // Try to borrow an object
2648             final String obj = pool.borrowObject("one");
2649             pool.returnObject("one", obj);
2650         }
2651     }
2652 
2653 
2654     // POOL-276
2655     @Test
2656     void testValidationOnCreateOnly() throws Exception {
2657         simpleFactory.enableValidation = true;
2658         gkoPool.setMaxTotal(1);
2659         gkoPool.setTestOnCreate(true);
2660         gkoPool.setTestOnBorrow(false);
2661         gkoPool.setTestOnReturn(false);
2662         gkoPool.setTestWhileIdle(false);
2663         final String o1 = gkoPool.borrowObject("KEY");
2664         assertEquals("KEY0", o1);
2665         final Timer t = new Timer();
2666         t.schedule(new TimerTask() {
2667             @Override
2668             public void run() {
2669                 gkoPool.returnObject("KEY", o1);
2670             }
2671         }, 3000);
2672         final String o2 = gkoPool.borrowObject("KEY");
2673         assertEquals("KEY0", o2);
2674         assertEquals(1, simpleFactory.validateCounter);
2675     }
2676 
2677     @Test
2678     void testWaiterCounts() throws Exception {
2679         final String[] keys = { "one", "two", "three" };
2680         final String[] objects = new String[keys.length];
2681         gkoPool.setMaxTotalPerKey(1);
2682         gkoPool.setBlockWhenExhausted(true);
2683         // Empty pool - waiter counts should be 0
2684         assertTrue(gkoPool.getNumWaitersByKey().isEmpty());
2685         assertEquals(0, gkoPool.getNumWaiters());
2686         // Exhaust the pool
2687         for (int i = 0; i < keys.length; i++) {
2688             objects[i] = gkoPool.borrowObject(keys[i]);
2689         }
2690         // Start 1 waiter for "one", 2 for "two", 3 for "three"
2691         // Configure them to hold borrowed object for 100ms
2692         final WaitingTestThread[] waiters = new WaitingTestThread[6];
2693         for (int i = 0; i < 3; i++) {
2694             for (int j = 0; j < i + 1; j++) {
2695                 waiters[i + j] = new WaitingTestThread(gkoPool, keys[i], 100);
2696                 waiters[i + j].start();
2697             }
2698         }
2699         Thread.sleep(10);
2700         // Check waiter counts
2701         assertEquals(6, gkoPool.getNumWaiters());
2702         for (int i = 0; i < 3; i++) {
2703             assertEquals(i + 1, gkoPool.getNumWaitersByKey().get(keys[i]).intValue());
2704         }
2705         // Return objects to the pool
2706         for (int i = 0; i < 3; i++) {
2707             gkoPool.returnObject(keys[i], objects[i]);
2708         }
2709         // One waiter for each key should get served, holding for 50ms
2710         Thread.sleep(10);
2711         // Recheck counts - should be 0 for "one", 1 for "two", 2 for "three
2712         assertEquals(3, gkoPool.getNumWaiters());
2713         for (int i = 0; i < 3; i++) {
2714             assertEquals(i, gkoPool.getNumWaitersByKey().get(keys[i]).intValue());
2715         }
2716         // Eventually, all get served
2717         for (int i = 0; i < 5; i++) {
2718             waiters[i].join();
2719         }
2720         // Should be no waiters now, one idle instance under each key
2721         assertEquals(0, gkoPool.getNumWaiters());
2722         for (int i = 0; i < 3; i++) {
2723             assertEquals(0, gkoPool.getNumWaitersByKey().get(keys[i]).intValue());
2724         }
2725         assertEquals(3, gkoPool.getNumIdle());
2726         for (final String key : keys) {
2727             assertEquals(1, gkoPool.getNumIdle(key));
2728         }
2729         // Clear the pool - should clear the map
2730         gkoPool.clear();
2731         assertTrue(gkoPool.getNumWaitersByKey().isEmpty());
2732         assertEquals(0, gkoPool.getNumWaiters());
2733         gkoPool.close();
2734     }
2735 
2736     /**
2737      * POOL-189
2738      *
2739      * @throws Exception May occur in some failure modes
2740      */
2741     @Test
2742     @Timeout(value = 60_000, unit = TimeUnit.MILLISECONDS)
2743     void testWhenExhaustedBlockClosePool() throws Exception {
2744         gkoPool.setMaxTotalPerKey(1);
2745         gkoPool.setBlockWhenExhausted(true);
2746         gkoPool.setMaxWaitMillis(-1);
2747         final String obj1 = gkoPool.borrowObject("a");
2748         // Make sure an object was obtained
2749         assertNotNull(obj1);
2750         // Create a separate thread to try and borrow another object
2751         final WaitingTestThread wtt = new WaitingTestThread(gkoPool, "a", 200);
2752         wtt.start();
2753         // Give wtt time to start
2754         Thread.sleep(200);
2755         // close the pool (Bug POOL-189)
2756         gkoPool.close();
2757         // Give interrupt time to take effect
2758         Thread.sleep(200);
2759         // Check thread was interrupted
2760         assertTrue(wtt.thrown instanceof InterruptedException);
2761     }
2762 }
2763