Skip to content

Commit e973e08

Browse files
committed
GenericObjectPool.borrowObject(Duration) doesn't obey its borrowMaxWait
Duration argument when the argument is different from GenericObjectPool.getMaxWaitDuration()
1 parent 1ccc008 commit e973e08

File tree

3 files changed

+71
-24
lines changed

3 files changed

+71
-24
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ The <action> type attribute can be add,update,fix,remove.
4848
<release version="2.12.1" date="YYYY-MM-DD" description="This is a feature and maintenance release (Java 8 or above).">
4949
<!-- FIX -->
5050
<action type="fix" dev="ggregory" due-to="Gary Gregory">Use java.time.Instant precision in org.apache.commons.pool2.impl.ThrowableCallStack.Snapshot throwable message.</action>
51+
<action type="fix" dev="ggregory" due-to="Gary Gregory">GenericObjectPool.borrowObject(Duration) doesn't obey its borrowMaxWait Duration argument when the argument is different from GenericObjectPool.getMaxWaitDuration().</action>
5152
<!-- ADD -->
5253
<!-- UPDATE -->
5354
<action type="update" dev="ggregory">Bump org.apache.commons:commons-parent from 62 to 73.</action>

src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public void addObject() throws Exception {
218218
if (factory == null) {
219219
throw new IllegalStateException("Cannot add objects without a factory.");
220220
}
221-
addIdleObject(create());
221+
addIdleObject(create(getMaxWaitDuration()));
222222
}
223223

224224
/**
@@ -282,46 +282,42 @@ public T borrowObject() throws Exception {
282282
*/
283283
public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
284284
assertOpen();
285-
285+
final Instant startInstant = Instant.now();
286+
final boolean negativeDuration = borrowMaxWaitDuration.isNegative();
287+
Duration remainingWaitDuration = borrowMaxWaitDuration;
286288
final AbandonedConfig ac = this.abandonedConfig;
287-
if (ac != null && ac.getRemoveAbandonedOnBorrow() && getNumIdle() < 2 &&
288-
getNumActive() > getMaxTotal() - 3) {
289+
if (ac != null && ac.getRemoveAbandonedOnBorrow() && getNumIdle() < 2 && getNumActive() > getMaxTotal() - 3) {
289290
removeAbandoned(ac);
290291
}
291-
292292
PooledObject<T> p = null;
293-
294293
// Get local copy of current config so it is consistent for entire
295294
// method execution
296295
final boolean blockWhenExhausted = getBlockWhenExhausted();
297-
298296
boolean create;
299-
final Instant waitTime = Instant.now();
300-
301297
while (p == null) {
298+
remainingWaitDuration = remainingWaitDuration.minus(durationSince(startInstant));
302299
create = false;
303300
p = idleObjects.pollFirst();
304301
if (p == null) {
305-
p = create();
302+
p = create(remainingWaitDuration);
306303
if (!PooledObject.isNull(p)) {
307304
create = true;
308305
}
309306
}
310307
if (blockWhenExhausted) {
311308
if (PooledObject.isNull(p)) {
312-
p = borrowMaxWaitDuration.isNegative() ? idleObjects.takeFirst() : idleObjects.pollFirst(borrowMaxWaitDuration);
309+
p = negativeDuration ? idleObjects.takeFirst() : idleObjects.pollFirst(remainingWaitDuration);
313310
}
314311
if (PooledObject.isNull(p)) {
315312
throw new NoSuchElementException(appendStats(
316-
"Timeout waiting for idle object, borrowMaxWaitDuration=" + borrowMaxWaitDuration));
313+
"Timeout waiting for idle object, borrowMaxWaitDuration=" + remainingWaitDuration));
317314
}
318315
} else if (PooledObject.isNull(p)) {
319316
throw new NoSuchElementException(appendStats("Pool exhausted"));
320317
}
321318
if (!p.allocate()) {
322319
p = null;
323320
}
324-
325321
if (!PooledObject.isNull(p)) {
326322
try {
327323
factory.activateObject(p);
@@ -366,12 +362,14 @@ public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
366362
}
367363
}
368364
}
369-
370-
updateStatsBorrow(p, Duration.between(waitTime, Instant.now()));
371-
365+
updateStatsBorrow(p, durationSince(startInstant));
372366
return p.getObject();
373367
}
374368

369+
private Duration durationSince(final Instant startInstant) {
370+
return Duration.between(startInstant, Instant.now());
371+
}
372+
375373
/**
376374
* Borrows an object from the pool using the specific waiting time which only
377375
* applies if {@link #getBlockWhenExhausted()} is true.
@@ -503,19 +501,19 @@ public void close() {
503501
* If the factory makeObject returns null, this method throws a NullPointerException.
504502
* </p>
505503
*
504+
* @param maxWaitDuration The time to wait for an object to become available.
506505
* @return The new wrapped pooled object or null.
507506
* @throws Exception if the object factory's {@code makeObject} fails
508507
*/
509-
private PooledObject<T> create() throws Exception {
508+
private PooledObject<T> create(final Duration maxWaitDuration) throws Exception {
510509
int localMaxTotal = getMaxTotal();
511510
// This simplifies the code later in this method
512511
if (localMaxTotal < 0) {
513512
localMaxTotal = Integer.MAX_VALUE;
514513
}
515514

516515
final Instant localStartInstant = Instant.now();
517-
final Duration maxWaitDurationRaw = getMaxWaitDuration();
518-
final Duration localMaxWaitDuration = maxWaitDurationRaw.isNegative() ? Duration.ZERO : maxWaitDurationRaw;
516+
final Duration localMaxWaitDuration = maxWaitDuration.isNegative() ? Duration.ZERO : maxWaitDuration;
519517

520518
// Flag that indicates if create should:
521519
// - TRUE: call the factory to create an object
@@ -549,9 +547,9 @@ private PooledObject<T> create() throws Exception {
549547
}
550548
}
551549

552-
// Do not block more if localMaxWaitDuration is set.
550+
// Do not block more if localMaxWaitDuration > 0.
553551
if (create == null && localMaxWaitDuration.compareTo(Duration.ZERO) > 0 &&
554-
Duration.between(localStartInstant, Instant.now()).compareTo(localMaxWaitDuration) >= 0) {
552+
durationSince(localStartInstant).compareTo(localMaxWaitDuration) >= 0) {
555553
create = Boolean.FALSE;
556554
}
557555
}
@@ -636,7 +634,7 @@ private void ensureIdle(final int idleCount, final boolean always) throws Except
636634
}
637635

638636
while (idleObjects.size() < idleCount) {
639-
final PooledObject<T> p = create();
637+
final PooledObject<T> p = create(getMaxWaitDuration());
640638
if (PooledObject.isNull(p)) {
641639
// Can't create objects, no reason to think another call to
642640
// create will work. Give up.

src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import javax.management.MBeanServer;
5151
import javax.management.ObjectName;
5252

53+
import org.apache.commons.lang3.time.DurationUtils;
5354
import org.apache.commons.pool2.BasePooledObjectFactory;
5455
import org.apache.commons.pool2.ObjectPool;
5556
import org.apache.commons.pool2.PoolUtils;
@@ -1074,6 +1075,53 @@ public void testBorrowObjectFairness() throws Exception {
10741075
}
10751076
}
10761077

1078+
@Test/* maxWaitMillis x2 + padding */
1079+
@Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
1080+
public void testBorrowObjectOverrideMaxWaitLarge() throws Exception {
1081+
try (final GenericObjectPool<String> pool = new GenericObjectPool<>(createSlowObjectFactory(60_000))) {
1082+
pool.setMaxTotal(1);
1083+
pool.setMaxWait(Duration.ofMillis(1_000)); // large
1084+
pool.setBlockWhenExhausted(false);
1085+
// thread1 tries creating a slow object to make pool full.
1086+
final WaitingTestThread thread1 = new WaitingTestThread(pool, 0);
1087+
thread1.start();
1088+
// Wait for thread1's reaching to create().
1089+
Thread.sleep(100);
1090+
// The test needs to make sure a wait happens in create().
1091+
final Duration d = DurationUtils.of(() -> assertThrows(NoSuchElementException.class, () -> pool.borrowObject(Duration.ofMillis(1)),
1092+
"borrowObject must fail quickly due to timeout parameter"));
1093+
final long millis = d.toMillis();
1094+
final long nanos = d.toNanos();
1095+
assertTrue(nanos > 0, () -> "borrowObject(Duration) argument not respected: " + nanos);
1096+
assertTrue(millis >= 0, () -> "borrowObject(Duration) argument not respected: " + millis); // not > 0 to account for spurious waits
1097+
assertTrue(millis < 50, () -> "borrowObject(Duration) argument not respected: " + millis);
1098+
}
1099+
}
1100+
1101+
@Test/* maxWaitMillis x2 + padding */
1102+
@Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
1103+
public void testBorrowObjectOverrideMaxWaitSmall() throws Exception {
1104+
try (final GenericObjectPool<String> pool = new GenericObjectPool<>(createSlowObjectFactory(60_000))) {
1105+
pool.setMaxTotal(1);
1106+
pool.setMaxWait(Duration.ofMillis(1)); // small
1107+
pool.setBlockWhenExhausted(false);
1108+
// thread1 tries creating a slow object to make pool full.
1109+
final WaitingTestThread thread1 = new WaitingTestThread(pool, 0);
1110+
thread1.start();
1111+
// Wait for thread1's reaching to create().
1112+
Thread.sleep(100);
1113+
// The test needs to make sure a wait happens in create().
1114+
final Duration d = DurationUtils.of(() -> assertThrows(NoSuchElementException.class, () -> pool.borrowObject(Duration.ofMillis(500)),
1115+
"borrowObject must fail slowly due to timeout parameter"));
1116+
final long millis = d.toMillis();
1117+
final long nanos = d.toNanos();
1118+
assertTrue(nanos > 0, () -> "borrowObject(Duration) argument not respected: " + nanos);
1119+
assertTrue(millis >= 0, () -> "borrowObject(Duration) argument not respected: " + millis); // not > 0 to account for spurious waits
1120+
assertTrue(millis < 600, () -> "borrowObject(Duration) argument not respected: " + millis);
1121+
assertTrue(millis > 490, () -> "borrowObject(Duration) argument not respected: " + millis);
1122+
}
1123+
}
1124+
10771125
@Test
10781126
public void testBorrowTimings() throws Exception {
10791127
// Borrow
@@ -2648,7 +2696,7 @@ public void testReturnBorrowObjectWithingMaxWaitDuration() throws Exception {
26482696
Thread.sleep(100);
26492697
// another one tries borrowObject. It should return within maxWaitMillis.
26502698
assertThrows(NoSuchElementException.class, () -> createSlowObjectFactoryPool.borrowObject(maxWaitDuration),
2651-
"borrowObject must fail due to timeout by maxWaitMillis");
2699+
"borrowObject must fail due to timeout by maxWait");
26522700
assertTrue(thread1.isAlive());
26532701
}
26542702
}
@@ -2667,7 +2715,7 @@ public void testReturnBorrowObjectWithingMaxWaitMillis() throws Exception {
26672715
Thread.sleep(100);
26682716
// another one tries borrowObject. It should return within maxWaitMillis.
26692717
assertThrows(NoSuchElementException.class, () -> createSlowObjectFactoryPool.borrowObject(maxWaitMillis),
2670-
"borrowObject must fail due to timeout by maxWaitMillis");
2718+
"borrowObject must fail due to timeout by maxWait");
26712719
assertTrue(thread1.isAlive());
26722720
}
26732721
}

0 commit comments

Comments
 (0)