Skip to content

[POOL-420] Exit the creation wait loop when maxWait expires - #456

Open
baldwk wants to merge 2 commits into
apache:POOL_2_Xfrom
baldwk:fix/pool-420-creation-timeout
Open

baldwk wants to merge 2 commits into
apache:POOL_2_Xfrom
baldwk:fix/pool-420-creation-timeout

Conversation

@baldwk

@baldwk baldwk commented Sep 28, 2026 •

Copy link
Copy Markdown

When other threads are creating objects and have filled the slots for a key, GenericKeyedObjectPool.create() can keep looping after maxWait expires. Once remainingWaitDuration becomes negative, the loop skips wait() but leaves create as null, so the borrower spins until another factory call finishes.

Exit the loop when the wait expires. The existing return path releases the reservation against maxTotal.

Follow-up to POOL-420, targeting POOL_2_X.

How to reproduce

Six threads borrow objects for the same key with maxTotal = 6, maxTotalPerKey = 1, and maxWait = 10s. Each creation attempt takes 3 seconds and fails. The threads take turns trying to create an object; before this fix, the last borrower can wait about 15 seconds before starting its own attempt.

The regression starts six borrowers together and simulates each connection failure with a 3-second delay followed by SocketTimeoutException. It checks that queued borrowers get NoSuchElementException and that the pool can reuse the released capacity.

mvn -B -ntp '-Dtest=TestGenericKeyedObjectPool#testMaxWaitDuringRepeatedCreationFailures' test

Validation (JDK 21)

  • The regression fails on the unmodified base (a0c13d55) after about 18 seconds and passes with the fix.
  • The regression and two existing max-wait tests pass. The full suite runs 405 tests with 0 failures, 0 errors, and 12 existing disabled tests.
  • Checkstyle, RAT, japicmp, Javadoc, and CPD pass. The default build stops at three SpotBugs findings; PMD reports one finding. Both match the unmodified base.

Related behavior to clarify: blockWhenExhausted=false

A separate local reproduction on Commons Pool 2.12.1 showed that blockWhenExhausted=false can still wait when in-flight factory calls occupy all creation slots for a key. I am not sure whether this is intentional.

The borrowObject Javadoc describes an exhausted sub-pool as having no available idle instances and no capacity to create new ones, and specifies NoSuchElementException when blockWhenExhausted is false. The expected behavior below is my reading of that contract; the last row is the part that needs clarification.

Scenario Expected with blockWhenExhausted=false Current behavior
An idle object is available Borrow the available object, subject to activation and validation. Matches.
No idle object is available, but creation capacity is available Attempt to create an object; the factory call itself can take time. Matches.
Capacity is occupied by objects that have already been created and borrowed Throw NoSuchElementException without waiting for capacity. Matches.
Capacity is occupied by objects still being created by other threads Throw without waiting for those factory calls to finish, if this counts as exhaustion under the contract. Waits for another factory call to finish, then retries the capacity check or throws.

For the last row, the reproduction used maxTotal=24, maxTotalPerKey=1, maxWait=1s, and blockWhenExhausted=false. One factory call held the creation slot for about 3 seconds and then failed. At about 1.3 seconds, a second borrower was still waiting inside create() and had not entered its own factory call. It eventually returned after about 3.03 seconds. The maxWait value is included for reproducibility; the question for false is whether it should wait for capacity at all.

This PR addresses expiry of a positive creation-wait budget. It does not implement immediate failure for the last scenario when blockWhenExhausted=false. Could maintainers confirm whether waiting for in-flight creation is intended in this mode, or whether this should be handled as a separate fix?

Exit the per-key creation wait when its initially positive budget is
exhausted, using the existing path to release the maxTotal reservation.
Leave zero and negative duration handling unchanged.

Add a latch-controlled regression that requires a waiting borrower to
finish before the in-flight creation completes and checks global capacity
is available to another key after the timeout.
@baldwk baldwk changed the title [POOL-420] Stop creation-capacity waits when a positive budget expires [POOL-420] Exit the creation wait loop when maxWait expires Sep 28, 2026
Replace the single blocked factory call with six concurrent borrowers.
Each creation attempt fails after three seconds, with maxWait set to ten
seconds. Verify that queued borrowers time out and release their pool
reservations.
@baldwk
baldwk changed the base branch from POOL_2_X to master September 28, 2026 07:43
@baldwk
baldwk force-pushed the fix/pool-420-creation-timeout branch from eeb23ed to a875b8d Compare September 28, 2026 07:43
@baldwk
baldwk changed the base branch from master to POOL_2_X September 28, 2026 07:52
@baldwk
baldwk force-pushed the fix/pool-420-creation-timeout branch from a875b8d to eeb23ed Compare September 28, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant