This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-pool.git


The following commit(s) were added to refs/heads/master by this push:
     new 466c7e51 The maximum wait time for GenericObjectPool.borrowObject(*) 
may exceed expectations due to a spurious thread wakeup
466c7e51 is described below

commit 466c7e51fb621cbf5deb67082af09b07d1120d5f
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Sun Nov 24 18:44:07 2024 -0500

    The maximum wait time for GenericObjectPool.borrowObject(*) may exceed
    expectations due to a spurious thread wakeup
---
 src/changes/changes.xml                              |  1 +
 .../apache/commons/pool3/impl/GenericObjectPool.java | 20 +++++++++++---------
 .../commons/pool3/impl/TestGenericObjectPool.java    |  4 ++--
 3 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1930f822..5933e84d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -68,6 +68,7 @@ The <action> type attribute can be add,update,fix,remove.
     <!-- FIX -->
     <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> 
     <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>
+    <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>
     <!-- ADD -->
     <!-- UPDATE -->
     <action type="update" dev="ggregory">Bump 
org.apache.commons:commons-parent from 62 to 73.</action>
diff --git a/src/main/java/org/apache/commons/pool3/impl/GenericObjectPool.java 
b/src/main/java/org/apache/commons/pool3/impl/GenericObjectPool.java
index 04d7f1b3..7d957177 100644
--- a/src/main/java/org/apache/commons/pool3/impl/GenericObjectPool.java
+++ b/src/main/java/org/apache/commons/pool3/impl/GenericObjectPool.java
@@ -88,7 +88,9 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
         "org.apache.commons.pool3:type=GenericObjectPool,name=";
 
     private static void wait(final Object obj, final Duration duration) throws 
InterruptedException {
-        obj.wait(duration.toMillis(), duration.getNano() % 1_000_000);
+        if (!duration.isNegative()) {
+            obj.wait(duration.toMillis(), duration.getNano() % 1_000_000);
+        }
     }
 
     private volatile String factoryType;
@@ -509,15 +511,14 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
      * @throws E if the object factory's {@code makeObject} fails
      */
     private PooledObject<T> create(final Duration maxWaitDuration) throws E {
+        final Instant startInstant = Instant.now();
+        Duration remainingWaitDuration = maxWaitDuration.isNegative() ? 
Duration.ZERO : maxWaitDuration;
         int localMaxTotal = getMaxTotal();
         // This simplifies the code later in this method
         if (localMaxTotal < 0) {
             localMaxTotal = Integer.MAX_VALUE;
         }
-
         final Instant localStartInstant = Instant.now();
-        final Duration localMaxWaitDuration = maxWaitDuration.isNegative() ? 
Duration.ZERO : maxWaitDuration;
-
         // Flag that indicates if create should:
         // - TRUE:  call the factory to create an object
         // - FALSE: return null
@@ -525,6 +526,8 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
         //          call the factory
         Boolean create = null;
         while (create == null) {
+            // remainingWaitDuration handles spurious wakeup from wait().
+            remainingWaitDuration = 
remainingWaitDuration.minus(durationSince(startInstant));
             synchronized (makeObjectCountLock) {
                 final long newCreateCount = createCount.incrementAndGet();
                 if (newCreateCount > localMaxTotal) {
@@ -542,7 +545,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
                         // fail so wait until they complete and then re-test if
                         // the pool is at capacity or not.
                         try {
-                            wait(makeObjectCountLock, localMaxWaitDuration);
+                            wait(makeObjectCountLock, remainingWaitDuration);
                         } catch (final InterruptedException e) {
                             // Don't surface exception type of internal 
locking mechanism.
                             throw cast(e);
@@ -554,10 +557,9 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
                     create = Boolean.TRUE;
                 }
             }
-
-            // Do not block more if localMaxWaitDuration > 0.
-            if (create == null && 
localMaxWaitDuration.compareTo(Duration.ZERO) > 0 &&
-                    
durationSince(localStartInstant).compareTo(localMaxWaitDuration) >= 0) {
+            // Do not block more if remainingWaitDuration > 0.
+            if (create == null && 
remainingWaitDuration.compareTo(Duration.ZERO) > 0 &&
+                    
durationSince(localStartInstant).compareTo(remainingWaitDuration) >= 0) {
                 create = Boolean.FALSE;
             }
         }
diff --git 
a/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java 
b/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java
index 6354d5b1..184a6db4 100644
--- a/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java
+++ b/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java
@@ -1941,8 +1941,8 @@ public class TestGenericObjectPool extends 
TestBaseObjectPool {
             assertFalse(thread1.isAlive());
             assertFalse(thread2.isAlive());
 
-            assertTrue(thread1.thrown instanceof UnsupportedCharsetException);
-            assertTrue(thread2.thrown instanceof UnsupportedCharsetException);
+            assertTrue(thread1.thrown instanceof UnsupportedCharsetException, 
() -> Objects.toString(thread1.thrown));
+            assertTrue(thread2.thrown instanceof UnsupportedCharsetException, 
() -> Objects.toString(thread2.thrown));
         }
     }
 

Reply via email to