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 380ecd91 GenericObjectPool.borrowObject(Duration) doesn't obey its 
borrowMaxWait Duration argument when the argument is different from 
GenericObjectPool.getMaxWaitDuration()
380ecd91 is described below

commit 380ecd91f62841aaceda04326372d982b9e6c1ef
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Sun Nov 24 18:20:48 2024 -0500

    GenericObjectPool.borrowObject(Duration) doesn't obey its borrowMaxWait
    Duration argument when the argument is different from
    GenericObjectPool.getMaxWaitDuration()
---
 src/changes/changes.xml                            | 12 ++++
 .../commons/pool3/impl/GenericObjectPool.java      | 48 +++++++-------
 .../commons/pool3/impl/TestGenericObjectPool.java  | 77 +++++++++++++++++++---
 3 files changed, 103 insertions(+), 34 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index fbabc3de..1930f822 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -64,6 +64,18 @@ The <action> type attribute can be add,update,fix,remove.
       Add ReslientPooledObjectFactory to provide resilence against factory 
outages.
     </action>
   </release> 
+  <release version="2.12.1" date="YYYY-MM-DD" description="This is a feature 
and maintenance release (Java 8 or above).">
+    <!-- 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>
+    <!-- ADD -->
+    <!-- UPDATE -->
+    <action type="update" dev="ggregory">Bump 
org.apache.commons:commons-parent from 62 to 73.</action>
+    <action type="update" dev="ggregory">Bump org.ow2.asm:asm-util from 9.5 to 
9.7.</action>
+    <action type="update" dev="ggregory" due-to="Gary Gregory">[test] Bump 
commons-lang3 from 3.13.0 to 3.16.0.</action>
+    <action type="update" dev="ggregory" due-to="Gary Gregory">[site] Bump 
org.apache.bcel:bcel from 6.7.0 to 6.8.2.</action>
+    <action type="update" dev="ggregory" due-to="Gary Gregory">[test] Bump 
org.hamcrest:hamcrest-library from 2.2 to 3.0.</action>
+  </release>
   <release version="2.12.0" date="2023-MM-DD" description="This is a feature 
and maintenance release (Java 8 or above).">
     <!-- FIX -->
     <action dev="psteitz" type="fix" issue="POOL-401">
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 d541afc1..04d7f1b3 100644
--- a/src/main/java/org/apache/commons/pool3/impl/GenericObjectPool.java
+++ b/src/main/java/org/apache/commons/pool3/impl/GenericObjectPool.java
@@ -220,7 +220,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
         if (factory == null) {
             throw new IllegalStateException("Cannot add objects without a 
factory.");
         }
-        addIdleObject(create());
+        addIdleObject(create(getMaxWaitDuration()));
     }
 
     /**
@@ -274,7 +274,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
      * available instances in request arrival order.
      * </p>
      *
-     * @param borrowMaxWaitDuration The time to wait for an object
+     * @param maxWaitDuration The time to wait for an object
      *                            to become available
      *
      * @return object instance from the pool
@@ -282,29 +282,26 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
      * @throws E if an object instance cannot be returned due to an error
      * @since 2.10.0
      */
-    public T borrowObject(final Duration borrowMaxWaitDuration) throws E {
+    public T borrowObject(final Duration maxWaitDuration) throws E {
         assertOpen();
-
+        final Instant startInstant = Instant.now();
+        final boolean negativeDuration = maxWaitDuration.isNegative();
+        Duration remainingWaitDuration = maxWaitDuration;
         final AbandonedConfig ac = this.abandonedConfig;
-        if (ac != null && ac.getRemoveAbandonedOnBorrow() && getNumIdle() < 2 
&&
-                getNumActive() > getMaxTotal() - 3) {
+        if (ac != null && ac.getRemoveAbandonedOnBorrow() && getNumIdle() < 2 
&& getNumActive() > getMaxTotal() - 3) {
             removeAbandoned(ac);
         }
-
         PooledObject<T> p = null;
-
         // Get local copy of current config so it is consistent for entire
         // method execution
         final boolean blockWhenExhausted = getBlockWhenExhausted();
-
         boolean create;
-        final Instant waitTime = Instant.now();
-
         while (p == null) {
+            remainingWaitDuration = 
remainingWaitDuration.minus(durationSince(startInstant));
             create = false;
             p = idleObjects.pollFirst();
             if (p == null) {
-                p = create();
+                p = create(remainingWaitDuration);
                 if (!PooledObject.isNull(p)) {
                     create = true;
                 }
@@ -312,7 +309,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
             if (blockWhenExhausted) {
                 if (PooledObject.isNull(p)) {
                     try {
-                        p = borrowMaxWaitDuration.isNegative() ? 
idleObjects.takeFirst() : idleObjects.pollFirst(borrowMaxWaitDuration);
+                        p = negativeDuration ? idleObjects.takeFirst() : 
idleObjects.pollFirst(maxWaitDuration);
                     } catch (final InterruptedException e) {
                         // Don't surface exception type of internal locking 
mechanism.
                         throw cast(e);
@@ -320,7 +317,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
                 }
                 if (PooledObject.isNull(p)) {
                     throw new NoSuchElementException(appendStats(
-                            "Timeout waiting for idle object, 
borrowMaxWaitDuration=" + borrowMaxWaitDuration));
+                            "Timeout waiting for idle object, 
borrowMaxWaitDuration=" + remainingWaitDuration));
                 }
             } else if (PooledObject.isNull(p)) {
                 throw new NoSuchElementException(appendStats("Pool 
exhausted"));
@@ -328,7 +325,6 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
             if (!p.allocate()) {
                 p = null;
             }
-
             if (!PooledObject.isNull(p)) {
                 try {
                     factory.activateObject(p);
@@ -373,9 +369,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
                 }
             }
         }
-
-        updateStatsBorrow(p, Duration.between(waitTime, Instant.now()));
-
+        updateStatsBorrow(p, durationSince(startInstant));
         return p.getObject();
     }
 
@@ -510,10 +504,11 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
      * If the factory makeObject returns null, this method throws a 
NullPointerException.
      * </p>
      *
+     * @param maxWaitDuration The time to wait for an object to become 
available.
      * @return The new wrapped pooled object or null.
      * @throws E if the object factory's {@code makeObject} fails
      */
-    private PooledObject<T> create() throws E {
+    private PooledObject<T> create(final Duration maxWaitDuration) throws E {
         int localMaxTotal = getMaxTotal();
         // This simplifies the code later in this method
         if (localMaxTotal < 0) {
@@ -521,8 +516,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
         }
 
         final Instant localStartInstant = Instant.now();
-        final Duration maxWaitDurationRaw = getMaxWaitDuration();
-        final Duration localMaxWaitDuration = maxWaitDurationRaw.isNegative() 
? Duration.ZERO : maxWaitDurationRaw;
+        final Duration localMaxWaitDuration = maxWaitDuration.isNegative() ? 
Duration.ZERO : maxWaitDuration;
 
         // Flag that indicates if create should:
         // - TRUE:  call the factory to create an object
@@ -561,9 +555,9 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
                 }
             }
 
-            // Do not block more if localMaxWaitDuration is set.
+            // Do not block more if localMaxWaitDuration > 0.
             if (create == null && 
localMaxWaitDuration.compareTo(Duration.ZERO) > 0 &&
-                    Duration.between(localStartInstant, 
Instant.now()).compareTo(localMaxWaitDuration) >= 0) {
+                    
durationSince(localStartInstant).compareTo(localMaxWaitDuration) >= 0) {
                 create = Boolean.FALSE;
             }
         }
@@ -600,10 +594,14 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
         }
 
         createdCount.incrementAndGet();
-        allObjects.put(IdentityWrapper.on(p), p);
+        allObjects.put(new IdentityWrapper<>(p.getObject()), p);
         return p;
     }
 
+    private Duration durationSince(final Instant startInstant) {
+        return Duration.between(startInstant, Instant.now());
+    }
+
     /**
      * Destroys a wrapped pooled object.
      *
@@ -648,7 +646,7 @@ public class GenericObjectPool<T, E extends Exception> 
extends BaseGenericObject
         }
 
         while (idleObjects.size() < idleCount) {
-            final PooledObject<T> p = create();
+            final PooledObject<T> p = create(getMaxWaitDuration());
             if (PooledObject.isNull(p)) {
                 // Can't create objects, no reason to think another call to
                 // create will work. Give up.
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 537b7e42..6354d5b1 100644
--- a/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java
+++ b/src/test/java/org/apache/commons/pool3/impl/TestGenericObjectPool.java
@@ -51,6 +51,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import javax.management.MBeanServer;
 import javax.management.ObjectName;
 
+import org.apache.commons.lang3.time.DurationUtils;
 import org.apache.commons.pool3.BasePooledObjectFactory;
 import org.apache.commons.pool3.ObjectPool;
 import org.apache.commons.pool3.PoolUtils;
@@ -1070,6 +1071,52 @@ public class TestGenericObjectPool extends 
TestBaseObjectPool {
         }
     }
 
+    @Test/* maxWaitMillis x2 + padding */
+    @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
+    public void testBorrowObjectOverrideMaxWaitLarge() throws Exception {
+        try (final GenericObjectPool<String, InterruptedException> pool = new 
GenericObjectPool<>(createSlowObjectFactory(60_000))) {
+            pool.setMaxTotal(1);
+            pool.setMaxWait(Duration.ofMillis(1_000)); // large
+            pool.setBlockWhenExhausted(false);
+            // thread1 tries creating a slow object to make pool full.
+            final WaitingTestThread<InterruptedException> thread1 = new 
WaitingTestThread<>(pool, 0);
+            thread1.start();
+            // Wait for thread1's reaching to create().
+            Thread.sleep(100);
+            // The test needs to make sure a wait happens in create().
+            final Duration d = DurationUtils.of(() -> 
assertThrows(NoSuchElementException.class, () -> 
pool.borrowObject(Duration.ofMillis(1)),
+                    "borrowObject must fail quickly due to timeout 
parameter"));
+            final long millis = d.toMillis();
+            final long nanos = d.toNanos();
+            assertTrue(nanos > 0, () -> "borrowObject(Duration) argument not 
respected: " + nanos);
+            assertTrue(millis >= 0, () -> "borrowObject(Duration) argument not 
respected: " + millis); // not > 0 to account for spurious waits
+            assertTrue(millis < 50, () -> "borrowObject(Duration) argument not 
respected: " + millis);
+        }
+    }
+
+    @Test/* maxWaitMillis x2 + padding */
+    @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
+    public void testBorrowObjectOverrideMaxWaitSmall() throws Exception {
+        try (final GenericObjectPool<String, InterruptedException> pool = new 
GenericObjectPool<>(createSlowObjectFactory(60_000))) {
+            pool.setMaxTotal(1);
+            pool.setMaxWait(Duration.ofMillis(1)); // small
+            pool.setBlockWhenExhausted(false);
+            // thread1 tries creating a slow object to make pool full.
+            final WaitingTestThread<InterruptedException> thread1 = new 
WaitingTestThread<>(pool, 0);
+            thread1.start();
+            // Wait for thread1's reaching to create().
+            Thread.sleep(100);
+            // The test needs to make sure a wait happens in create().
+            final Duration d = DurationUtils.of(() -> 
assertThrows(NoSuchElementException.class, () -> 
pool.borrowObject(Duration.ofMillis(500)),
+                    "borrowObject must fail slowly due to timeout parameter"));
+            final long millis = d.toMillis();
+            final long nanos = d.toNanos();
+            assertTrue(nanos > 0, () -> "borrowObject(Duration) argument not 
respected: " + nanos);
+            assertTrue(millis >= 0, () -> "borrowObject(Duration) argument not 
respected: " + millis); // not > 0 to account for spurious waits
+            assertTrue(millis < 600, () -> "borrowObject(Duration) argument 
not respected: " + millis);
+            assertTrue(millis > 490, () -> "borrowObject(Duration) argument 
not respected: " + millis);
+        }
+    }
     @Test
     public void testBorrowTimings() throws Exception {
         // Borrow
@@ -2642,28 +2689,40 @@ public class TestGenericObjectPool extends 
TestBaseObjectPool {
         assertEquals(1, genericObjectPool.getNumIdle());
     }
 
+    @Test/* maxWaitMillis x2 + padding */
+    @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
+    public void testReturnBorrowObjectWithingMaxWaitDuration() throws 
Exception {
+        final Duration maxWaitDuration = Duration.ofMillis(500);
+        try (final GenericObjectPool<String, InterruptedException> 
createSlowObjectFactoryPool = new 
GenericObjectPool<>(createSlowObjectFactory(60_000))) {
+            createSlowObjectFactoryPool.setMaxTotal(1);
+            createSlowObjectFactoryPool.setMaxWait(maxWaitDuration);
+            // thread1 tries creating a slow object to make pool full.
+            final WaitingTestThread<InterruptedException> thread1 = new 
WaitingTestThread<>(createSlowObjectFactoryPool, 0);
+            thread1.start();
+            // Wait for thread1's reaching to create().
+            Thread.sleep(100);
+            // another one tries borrowObject. It should return within 
maxWaitMillis.
+            assertThrows(NoSuchElementException.class, () -> 
createSlowObjectFactoryPool.borrowObject(maxWaitDuration),
+                    "borrowObject must fail due to timeout by maxWaitMillis");
+            assertTrue(thread1.isAlive());
+        }
+    }
+    
     @Test /* maxWaitMillis x2 + padding */
     @Timeout(value = 1200, unit = TimeUnit.MILLISECONDS)
     public void testReturnBorrowObjectWithingMaxWaitMillis() throws Exception {
         final long maxWaitMillis = 500;
-
-        try (final GenericObjectPool<String, InterruptedException> 
createSlowObjectFactoryPool = new GenericObjectPool<>(
-                createSlowObjectFactory(60000))) {
+        try (final GenericObjectPool<String, InterruptedException> 
createSlowObjectFactoryPool = new 
GenericObjectPool<>(createSlowObjectFactory(60000))) {
             createSlowObjectFactoryPool.setMaxTotal(1);
             
createSlowObjectFactoryPool.setMaxWait(Duration.ofMillis(maxWaitMillis));
-
             // thread1 tries creating a slow object to make pool full.
-            final WaitingTestThread<InterruptedException> thread1 = new 
WaitingTestThread<>(createSlowObjectFactoryPool,
-                    0);
+            final WaitingTestThread<InterruptedException> thread1 = new 
WaitingTestThread<>(createSlowObjectFactoryPool, 0);
             thread1.start();
-
             // Wait for thread1's reaching to create().
             Thread.sleep(100);
-
             // another one tries borrowObject. It should return within 
maxWaitMillis.
             assertThrows(NoSuchElementException.class, () -> 
createSlowObjectFactoryPool.borrowObject(maxWaitMillis),
                     "borrowObject must fail due to timeout by maxWaitMillis");
-
             assertTrue(thread1.isAlive());
         }
     }

Reply via email to