diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java index c9f4a177e..f67205c2d 100644 --- a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java +++ b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java @@ -860,7 +860,9 @@ private PooledObject create(final K key, final Duration maxWaitDuration) thro // bring the pool to capacity. Those calls might also // fail so wait until they complete and then re-test if // the pool is at capacity or not. - if (!remainingWaitDuration.isNegative()) { + if (PoolImplUtils.isPositive(maxWaitDuration) && !PoolImplUtils.isPositive(remainingWaitDuration)) { + create = Boolean.FALSE; + } else if (!remainingWaitDuration.isNegative()) { objectDeque.makeObjectCountLock.wait(remainingWaitDuration.toMillis(), remainingWaitDuration.getNano() % 1_000_000); } diff --git a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java index 9f6e687ff..a8e660b69 100644 --- a/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java +++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericKeyedObjectPool.java @@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.lang.management.ManagementFactory; +import java.net.SocketTimeoutException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -40,6 +41,7 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -2105,6 +2107,67 @@ void testMaxWaitMultiThreaded() throws Exception { } + @Test + @Timeout(30) + void testMaxWaitDuringRepeatedCreationFailures() throws Exception { + final int borrowerCount = 6; + final CountDownLatch borrowersReady = new CountDownLatch(borrowerCount); + final CountDownLatch startBorrowing = new CountDownLatch(1); + final AtomicInteger creationCount = new AtomicInteger(); + final BaseKeyedPooledObjectFactory factory = new BaseKeyedPooledObjectFactory() { + @Override + public String create(final String key) throws Exception { + if ("a".equals(key)) { + creationCount.incrementAndGet(); + Thread.sleep(3000); + throw new SocketTimeoutException("Simulated connection timeout"); + } + return key; + } + + @Override + public PooledObject wrap(final String value) { + return new DefaultPooledObject<>(value); + } + }; + final ExecutorService executor = Executors.newFixedThreadPool(borrowerCount, new DaemonThreadFactory()); + try (GenericKeyedObjectPool pool = new GenericKeyedObjectPool<>(factory)) { + pool.setMaxTotal(borrowerCount); + pool.setMaxTotalPerKey(1); + pool.setMaxWait(Duration.ofSeconds(10)); + final List> borrowers = new ArrayList<>(); + for (int i = 0; i < borrowerCount; i++) { + borrowers.add(executor.submit(() -> { + borrowersReady.countDown(); + startBorrowing.await(); + return pool.borrowObject("a"); + })); + } + assertTrue(borrowersReady.await(5, TimeUnit.SECONDS)); + startBorrowing.countDown(); + int poolTimeouts = 0; + for (final Future borrower : borrowers) { + final ExecutionException exception = assertThrows(ExecutionException.class, borrower::get); + if (exception.getCause() instanceof NoSuchElementException) { + poolTimeouts++; + } else { + assertTrue(exception.getCause() instanceof SocketTimeoutException); + } + } + assertTrue(creationCount.get() > 1, "Multiple borrowers must attempt creation"); + assertTrue(poolTimeouts > 0, "Borrowers waiting for a creation slot must time out"); + // Failed and timed-out borrows must release their reservations against maxTotal. + pool.setMaxTotal(1); + final String other = pool.borrowObject("b", Duration.ZERO); + assertEquals("b", other); + pool.returnObject("b", other); + } finally { + startBorrowing.countDown(); + executor.shutdownNow(); + assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); + } + } + /** * JIRA: POOL-420 (clone of POOL-418 for GKOP) *