4040import java .util .concurrent .CancellationException ;
4141import java .util .concurrent .ExecutionException ;
4242import java .util .concurrent .Future ;
43- import java .util .concurrent .TimeUnit ;
44- import java .util .concurrent .TimeoutException ;
4543
4644import org .apache .hc .core5 .io .CloseMode ;
4745import org .apache .hc .core5 .util .TimeValue ;
5048final class ConnPoolContractFuzzer {
5149
5250 private static final Timeout REQUEST_TIMEOUT = Timeout .ofSeconds (30 );
51+ private static final Timeout NO_WAIT = Timeout .ofMilliseconds (0 );
5352 private static final TimeValue IDLE_TIME = TimeValue .ofMilliseconds (1 );
5453
5554 private ConnPoolContractFuzzer () {
@@ -113,9 +112,10 @@ static void run(
113112 case 1 :
114113 case 2 : {
115114 final String route = routes .get (rnd .nextInt (routes .size ()));
116- final Object state = (rnd .nextInt (4 ) == 0 ) ? null : rnd .nextInt (3 );
115+ final Object state = (rnd .nextInt (4 ) == 0 ) ? null : Integer . valueOf ( rnd .nextInt (3 ) );
117116 trace .add (step + ": lease(" + route + ", state=" + state + ")" );
118- final Future <PoolEntry <String , PoolTestSupport .DummyConn >> f = pool .lease (route , state , REQUEST_TIMEOUT , null );
117+ final Future <PoolEntry <String , PoolTestSupport .DummyConn >> f =
118+ pool .lease (route , state , REQUEST_TIMEOUT , null );
119119 pending .add (new PendingLease (state , f ));
120120 break ;
121121 }
@@ -199,6 +199,9 @@ static void run(
199199 }
200200 }
201201
202+ // Drain again so invariants observe the same state we model.
203+ drainDoneFutures (pending , leased , leasedSet );
204+
202205 assertCoreInvariants (pool );
203206
204207 if (poolType != PoolTestSupport .PoolType .LAX ) {
@@ -212,6 +215,7 @@ static void run(
212215
213216 } catch (final AssertionError ex ) {
214217 fail ("Pool=" + poolType + " seed=" + seed
218+ + "\n Assertion: " + ex .getMessage ()
215219 + "\n Repro: -Dhc.pool.fuzz.seed=" + seed
216220 + "\n Trace(last 80):\n " + tail (trace , 80 ), ex );
217221 } finally {
@@ -250,33 +254,49 @@ private static void drainDoneFutures(
250254 }
251255 }
252256
257+ /**
258+ * Fast, deterministic activity trigger. No waiting.
259+ */
253260 private static void nudge (
254261 final ManagedConnPool <String , PoolTestSupport .DummyConn > pool ,
255262 final List <String > routes ) {
256263
264+ if (pool instanceof StrictConnPool ) {
265+ ((StrictConnPool <?, ?>) pool ).validatePendingRequests ();
266+ return ;
267+ }
268+ if (pool instanceof LaxConnPool ) {
269+ ((LaxConnPool <?, ?>) pool ).validatePendingRequests ();
270+ return ;
271+ }
272+
273+ // OFFLOCK: avoid waiting and avoid enqueuing if saturated.
257274 for (final String route : routes ) {
258- final Future <PoolEntry <String , PoolTestSupport .DummyConn >> f =
259- pool .lease (route , null , Timeout .ofMilliseconds (200 ), null );
275+ final PoolStats rs = pool .getStats (route );
276+ final int max = pool .getMaxPerRoute (route );
277+ if (max > 0 && rs .getLeased () + rs .getAvailable () >= max ) {
278+ continue ;
279+ }
280+
281+ final Future <PoolEntry <String , PoolTestSupport .DummyConn >> f = pool .lease (route , null , NO_WAIT , null );
282+ if (!f .isDone ()) {
283+ f .cancel (true );
284+ continue ;
285+ }
260286 try {
261- final PoolEntry <String , PoolTestSupport .DummyConn > entry = f .get (200 , TimeUnit . MILLISECONDS );
287+ final PoolEntry <String , PoolTestSupport .DummyConn > entry = f .get ();
262288 if (entry != null ) {
263289 if (!entry .hasConnection ()) {
264290 entry .assignConnection (new PoolTestSupport .DummyConn ());
265291 }
266292 pool .release (entry , true );
267293 }
268- } catch (final TimeoutException ex ) {
269- f .cancel (true );
270294 } catch (final Exception ignore ) {
271295 // ignore
272296 }
273297 }
274298 }
275299
276- /**
277- * Adjustment: cleanup must not drop futures that completed concurrently, otherwise we
278- * can leave a successfully leased entry unreleased and report a false “leak”.
279- */
280300 private static void cleanupAndAssertQuiescent (
281301 final PoolTestSupport .PoolType poolType ,
282302 final ManagedConnPool <String , PoolTestSupport .DummyConn > pool ,
@@ -298,7 +318,6 @@ private static void cleanupAndAssertQuiescent(
298318 pool .release (e , true );
299319 }
300320
301- // Drain any done futures we didn’t observe via drainDoneFutures (race window).
302321 for (final Iterator <PendingLease > it = pending .iterator (); it .hasNext (); ) {
303322 final PendingLease p = it .next ();
304323 if (!p .future .isDone ()) {
@@ -320,7 +339,6 @@ private static void cleanupAndAssertQuiescent(
320339 }
321340 }
322341
323- // Encourage flushing of cancelled waiters without using impl-specific APIs.
324342 nudge (pool , routes );
325343 pool .closeExpired ();
326344 pool .closeIdle (IDLE_TIME );
@@ -344,12 +362,15 @@ private static void cleanupAndAssertQuiescent(
344362 }
345363
346364 private static void assertCoreInvariants (final ManagedConnPool <String , PoolTestSupport .DummyConn > pool ) {
365+ assertTrue (pool .getMaxTotal () >= 0 , "maxTotal negative" );
366+
347367 final PoolStats total = pool .getTotalStats ();
348368 assertTrue (total .getLeased () >= 0 , "total.leased" );
349369 assertTrue (total .getPending () >= 0 , "total.pending" );
350370 assertTrue (total .getAvailable () >= 0 , "total.available" );
351371
352372 for (final String route : pool .getRoutes ()) {
373+ assertTrue (pool .getMaxPerRoute (route ) >= 0 , "maxPerRoute negative" );
353374 final PoolStats rs = pool .getStats (route );
354375 assertTrue (rs .getLeased () >= 0 , "route.leased" );
355376 assertTrue (rs .getPending () >= 0 , "route.pending" );
0 commit comments