Skip to content

Commit 466c7e5

Browse files
committed
The maximum wait time for GenericObjectPool.borrowObject(*) may exceed
expectations due to a spurious thread wakeup
1 parent 380ecd9 commit 466c7e5

File tree

3 files changed

+14
-11
lines changed

3 files changed

+14
-11
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ The <action> type attribute can be add,update,fix,remove.
6868
<!-- FIX -->
6969
<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>
7070
<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>
71+
<action type="fix" issue="POOL-418" dev="ggregory" due-to="Gary Gregory">The maximum wait time for GenericObjectPool.borrowObject(*) may exceed expectations due to a spurious thread wakeup.</action>
7172
<!-- ADD -->
7273
<!-- UPDATE -->
7374
<action type="update" dev="ggregory">Bump org.apache.commons:commons-parent from 62 to 73.</action>

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ public class GenericObjectPool<T, E extends Exception> extends BaseGenericObject
8888
"org.apache.commons.pool3:type=GenericObjectPool,name=";
8989

9090
private static void wait(final Object obj, final Duration duration) throws InterruptedException {
91-
obj.wait(duration.toMillis(), duration.getNano() % 1_000_000);
91+
if (!duration.isNegative()) {
92+
obj.wait(duration.toMillis(), duration.getNano() % 1_000_000);
93+
}
9294
}
9395

9496
private volatile String factoryType;
@@ -509,22 +511,23 @@ public void close() {
509511
* @throws E if the object factory's {@code makeObject} fails
510512
*/
511513
private PooledObject<T> create(final Duration maxWaitDuration) throws E {
514+
final Instant startInstant = Instant.now();
515+
Duration remainingWaitDuration = maxWaitDuration.isNegative() ? Duration.ZERO : maxWaitDuration;
512516
int localMaxTotal = getMaxTotal();
513517
// This simplifies the code later in this method
514518
if (localMaxTotal < 0) {
515519
localMaxTotal = Integer.MAX_VALUE;
516520
}
517-
518521
final Instant localStartInstant = Instant.now();
519-
final Duration localMaxWaitDuration = maxWaitDuration.isNegative() ? Duration.ZERO : maxWaitDuration;
520-
521522
// Flag that indicates if create should:
522523
// - TRUE: call the factory to create an object
523524
// - FALSE: return null
524525
// - null: loop and re-test the condition that determines whether to
525526
// call the factory
526527
Boolean create = null;
527528
while (create == null) {
529+
// remainingWaitDuration handles spurious wakeup from wait().
530+
remainingWaitDuration = remainingWaitDuration.minus(durationSince(startInstant));
528531
synchronized (makeObjectCountLock) {
529532
final long newCreateCount = createCount.incrementAndGet();
530533
if (newCreateCount > localMaxTotal) {
@@ -542,7 +545,7 @@ private PooledObject<T> create(final Duration maxWaitDuration) throws E {
542545
// fail so wait until they complete and then re-test if
543546
// the pool is at capacity or not.
544547
try {
545-
wait(makeObjectCountLock, localMaxWaitDuration);
548+
wait(makeObjectCountLock, remainingWaitDuration);
546549
} catch (final InterruptedException e) {
547550
// Don't surface exception type of internal locking mechanism.
548551
throw cast(e);
@@ -554,10 +557,9 @@ private PooledObject<T> create(final Duration maxWaitDuration) throws E {
554557
create = Boolean.TRUE;
555558
}
556559
}
557-
558-
// Do not block more if localMaxWaitDuration > 0.
559-
if (create == null && localMaxWaitDuration.compareTo(Duration.ZERO) > 0 &&
560-
durationSince(localStartInstant).compareTo(localMaxWaitDuration) >= 0) {
560+
// Do not block more if remainingWaitDuration > 0.
561+
if (create == null && remainingWaitDuration.compareTo(Duration.ZERO) > 0 &&
562+
durationSince(localStartInstant).compareTo(remainingWaitDuration) >= 0) {
561563
create = Boolean.FALSE;
562564
}
563565
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,8 +1941,8 @@ public void testFailingFactoryDoesNotBlockThreads() throws Exception {
19411941
assertFalse(thread1.isAlive());
19421942
assertFalse(thread2.isAlive());
19431943

1944-
assertTrue(thread1.thrown instanceof UnsupportedCharsetException);
1945-
assertTrue(thread2.thrown instanceof UnsupportedCharsetException);
1944+
assertTrue(thread1.thrown instanceof UnsupportedCharsetException, () -> Objects.toString(thread1.thrown));
1945+
assertTrue(thread2.thrown instanceof UnsupportedCharsetException, () -> Objects.toString(thread2.thrown));
19461946
}
19471947
}
19481948

0 commit comments

Comments
 (0)