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