View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.core5.pool;
28  
29  import java.util.Collections;
30  import java.util.concurrent.CancellationException;
31  import java.util.concurrent.CountDownLatch;
32  import java.util.concurrent.ExecutionException;
33  import java.util.concurrent.Future;
34  import java.util.concurrent.TimeUnit;
35  
36  import org.apache.hc.core5.http.HttpConnection;
37  import org.apache.hc.core5.io.CloseMode;
38  import org.apache.hc.core5.util.DeadlineTimeoutException;
39  import org.apache.hc.core5.util.TimeValue;
40  import org.apache.hc.core5.util.Timeout;
41  import org.junit.jupiter.api.Assertions;
42  import org.junit.jupiter.api.Test;
43  import org.mockito.ArgumentMatchers;
44  import org.mockito.Mockito;
45  
46  public class TestStrictConnPool {
47  
48      @Test
49      public void testEmptyPool() throws Exception {
50          try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10)) {
51              final PoolStats totals = pool.getTotalStats();
52              Assertions.assertEquals(0, totals.getAvailable());
53              Assertions.assertEquals(0, totals.getLeased());
54              Assertions.assertEquals(0, totals.getPending());
55              Assertions.assertEquals(10, totals.getMax());
56              Assertions.assertEquals(Collections.emptySet(), pool.getRoutes());
57              final PoolStats stats = pool.getStats("somehost");
58              Assertions.assertEquals(0, stats.getAvailable());
59              Assertions.assertEquals(0, stats.getLeased());
60              Assertions.assertEquals(0, stats.getPending());
61              Assertions.assertEquals(2, stats.getMax());
62              Assertions.assertEquals("[leased: 0][available: 0][pending: 0]", pool.toString());
63          }
64      }
65  
66      @Test
67      public void testInvalidConstruction() throws Exception {
68          Assertions.assertThrows(IllegalArgumentException.class, () ->
69                  new StrictConnPool<String, HttpConnection>(-1, 1));
70          Assertions.assertThrows(IllegalArgumentException.class, () ->
71                  new StrictConnPool<String, HttpConnection>(1, -1));
72      }
73  
74      @Test
75      public void testLeaseRelease() throws Exception {
76          final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
77          final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
78          final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
79  
80          try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10)) {
81              final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
82              final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
83              final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
84  
85              final PoolEntry<String, HttpConnection> entry1 = future1.get();
86              Assertions.assertNotNull(entry1);
87              entry1.assignConnection(conn1);
88              final PoolEntry<String, HttpConnection> entry2 = future2.get();
89              Assertions.assertNotNull(entry2);
90              entry2.assignConnection(conn2);
91              final PoolEntry<String, HttpConnection> entry3 = future3.get();
92              Assertions.assertNotNull(entry3);
93              entry3.assignConnection(conn3);
94  
95              pool.release(entry1, true);
96              pool.release(entry2, true);
97              pool.release(entry3, false);
98              Mockito.verify(conn1, Mockito.never()).close(ArgumentMatchers.any());
99              Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any());
100             Mockito.verify(conn3, Mockito.times(1)).close(CloseMode.GRACEFUL);
101 
102             final PoolStats totals = pool.getTotalStats();
103             Assertions.assertEquals(2, totals.getAvailable());
104             Assertions.assertEquals(0, totals.getLeased());
105             Assertions.assertEquals(0, totals.getPending());
106         }
107     }
108 
109     @Test
110     public void testLeaseInvalid() throws Exception {
111         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10)) {
112             Assertions.assertThrows(NullPointerException.class, () ->
113                     pool.lease(null, null, Timeout.ZERO_MILLISECONDS, null));
114             Assertions.assertThrows(NullPointerException.class, () ->
115                     pool.lease("somehost", null, null, null));
116     }
117     }
118 
119     @Test
120     public void testReleaseUnknownEntry() throws Exception {
121         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2)) {
122                 Assertions.assertThrows(IllegalStateException.class, () ->
123                     pool.release(new PoolEntry<>("somehost"), true));
124         }
125     }
126 
127     @Test
128     public void testMaxLimits() throws Exception {
129         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
130         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
131         final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
132 
133         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10)) {
134             pool.setMaxPerRoute("somehost", 2);
135             pool.setMaxPerRoute("otherhost", 1);
136             pool.setMaxTotal(3);
137 
138             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
139             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
140             final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
141 
142             final PoolEntry<String, HttpConnection> entry1 = future1.get();
143             Assertions.assertNotNull(entry1);
144             entry1.assignConnection(conn1);
145             final PoolEntry<String, HttpConnection> entry2 = future2.get();
146             Assertions.assertNotNull(entry2);
147             entry2.assignConnection(conn2);
148             final PoolEntry<String, HttpConnection> entry3 = future3.get();
149             Assertions.assertNotNull(entry3);
150             entry3.assignConnection(conn3);
151 
152             pool.release(entry1, true);
153             pool.release(entry2, true);
154             pool.release(entry3, true);
155 
156             final PoolStats totals = pool.getTotalStats();
157             Assertions.assertEquals(3, totals.getAvailable());
158             Assertions.assertEquals(0, totals.getLeased());
159             Assertions.assertEquals(0, totals.getPending());
160 
161             final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("somehost", null);
162             final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", null);
163             final Future<PoolEntry<String, HttpConnection>> future6 = pool.lease("otherhost", null);
164             final Future<PoolEntry<String, HttpConnection>> future7 = pool.lease("somehost", null);
165             final Future<PoolEntry<String, HttpConnection>> future8 = pool.lease("somehost", null);
166             final Future<PoolEntry<String, HttpConnection>> future9 = pool.lease("otherhost", null);
167 
168             Assertions.assertTrue(future4.isDone());
169             final PoolEntry<String, HttpConnection> entry4 = future4.get();
170             Assertions.assertNotNull(entry4);
171             Assertions.assertSame(conn2, entry4.getConnection());
172 
173             Assertions.assertTrue(future5.isDone());
174             final PoolEntry<String, HttpConnection> entry5 = future5.get();
175             Assertions.assertNotNull(entry5);
176             Assertions.assertSame(conn1, entry5.getConnection());
177 
178             Assertions.assertTrue(future6.isDone());
179             final PoolEntry<String, HttpConnection> entry6 = future6.get();
180             Assertions.assertNotNull(entry6);
181             Assertions.assertSame(conn3, entry6.getConnection());
182 
183             Assertions.assertFalse(future7.isDone());
184             Assertions.assertFalse(future8.isDone());
185             Assertions.assertFalse(future9.isDone());
186 
187             pool.release(entry4, true);
188             pool.release(entry5, false);
189             pool.release(entry6, true);
190 
191             Assertions.assertTrue(future7.isDone());
192             final PoolEntry<String, HttpConnection> entry7 = future7.get();
193             Assertions.assertNotNull(entry7);
194             Assertions.assertSame(conn2, entry7.getConnection());
195 
196             Assertions.assertTrue(future8.isDone());
197             final PoolEntry<String, HttpConnection> entry8 = future8.get();
198             Assertions.assertNotNull(entry8);
199             Assertions.assertNull(entry8.getConnection());
200 
201             Assertions.assertTrue(future9.isDone());
202             final PoolEntry<String, HttpConnection> entry9 = future9.get();
203             Assertions.assertNotNull(entry9);
204             Assertions.assertSame(conn3, entry9.getConnection());
205         }
206     }
207 
208     @Test
209     public void testConnectionRedistributionOnTotalMaxLimit() throws Exception {
210         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
211         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
212         final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
213         final HttpConnection conn4 = Mockito.mock(HttpConnection.class);
214         final HttpConnection conn5 = Mockito.mock(HttpConnection.class);
215 
216         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10)) {
217             pool.setMaxPerRoute("somehost", 2);
218             pool.setMaxPerRoute("otherhost", 2);
219             pool.setMaxTotal(2);
220 
221             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
222             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
223             final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
224             final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("otherhost", null);
225 
226             Assertions.assertTrue(future1.isDone());
227             final PoolEntry<String, HttpConnection> entry1 = future1.get();
228             Assertions.assertNotNull(entry1);
229             Assertions.assertFalse(entry1.hasConnection());
230             entry1.assignConnection(conn1);
231             Assertions.assertTrue(future2.isDone());
232             final PoolEntry<String, HttpConnection> entry2 = future2.get();
233             Assertions.assertNotNull(entry2);
234             Assertions.assertFalse(entry2.hasConnection());
235             entry2.assignConnection(conn2);
236 
237             Assertions.assertFalse(future3.isDone());
238             Assertions.assertFalse(future4.isDone());
239 
240             PoolStats totals = pool.getTotalStats();
241             Assertions.assertEquals(0, totals.getAvailable());
242             Assertions.assertEquals(2, totals.getLeased());
243             Assertions.assertEquals(2, totals.getPending());
244 
245             pool.release(entry1, true);
246             pool.release(entry2, true);
247 
248             Assertions.assertTrue(future3.isDone());
249             final PoolEntry<String, HttpConnection> entry3 = future3.get();
250             Assertions.assertNotNull(entry3);
251             Assertions.assertFalse(entry3.hasConnection());
252             entry3.assignConnection(conn3);
253             Assertions.assertTrue(future4.isDone());
254             final PoolEntry<String, HttpConnection> entry4 = future4.get();
255             Assertions.assertNotNull(entry4);
256             Assertions.assertFalse(entry4.hasConnection());
257             entry4.assignConnection(conn4);
258 
259             totals = pool.getTotalStats();
260             Assertions.assertEquals(0, totals.getAvailable());
261             Assertions.assertEquals(2, totals.getLeased());
262             Assertions.assertEquals(0, totals.getPending());
263 
264             final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", null);
265             final Future<PoolEntry<String, HttpConnection>> future6 = pool.lease("otherhost", null);
266 
267             pool.release(entry3, true);
268             pool.release(entry4, true);
269 
270             Assertions.assertTrue(future5.isDone());
271             final PoolEntry<String, HttpConnection> entry5 = future5.get();
272             Assertions.assertNotNull(entry5);
273             Assertions.assertFalse(entry5.hasConnection());
274             entry5.assignConnection(conn5);
275             Assertions.assertTrue(future6.isDone());
276             final PoolEntry<String, HttpConnection> entry6 = future6.get();
277             Assertions.assertNotNull(entry6);
278             Assertions.assertTrue(entry6.hasConnection());
279             Assertions.assertSame(conn4, entry6.getConnection());
280 
281             totals = pool.getTotalStats();
282             Assertions.assertEquals(0, totals.getAvailable());
283             Assertions.assertEquals(2, totals.getLeased());
284             Assertions.assertEquals(0, totals.getPending());
285 
286             pool.release(entry5, true);
287             pool.release(entry6, true);
288 
289             totals = pool.getTotalStats();
290             Assertions.assertEquals(2, totals.getAvailable());
291             Assertions.assertEquals(0, totals.getLeased());
292             Assertions.assertEquals(0, totals.getPending());}
293     }
294 
295     @Test
296     public void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exception {
297         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
298         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
299 
300         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10)) {
301             pool.setMaxPerRoute("somehost", 2);
302             pool.setMaxTotal(2);
303 
304             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
305             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
306 
307             Assertions.assertTrue(future1.isDone());
308             final PoolEntry<String, HttpConnection> entry1 = future1.get();
309             entry1.assignConnection(conn1);
310             Assertions.assertNotNull(entry1);
311             Assertions.assertTrue(future2.isDone());
312             final PoolEntry<String, HttpConnection> entry2 = future2.get();
313             Assertions.assertNotNull(entry2);
314             entry2.assignConnection(conn2);
315 
316             PoolStats totals = pool.getTotalStats();
317             Assertions.assertEquals(0, totals.getAvailable());
318             Assertions.assertEquals(2, totals.getLeased());
319             Assertions.assertEquals(0, totals.getPending());
320 
321             entry1.updateState("some-stuff");
322             pool.release(entry1, true);
323             entry2.updateState("some-stuff");
324             pool.release(entry2, true);
325 
326             final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("somehost", "some-stuff");
327             final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("somehost", "some-stuff");
328 
329             Assertions.assertTrue(future1.isDone());
330             final PoolEntry<String, HttpConnection> entry3 = future3.get();
331             Assertions.assertNotNull(entry3);
332             Assertions.assertSame(conn2, entry3.getConnection());
333             Assertions.assertTrue(future4.isDone());
334             final PoolEntry<String, HttpConnection> entry4 = future4.get();
335             Assertions.assertNotNull(entry4);
336             Assertions.assertSame(conn1, entry4.getConnection());
337 
338             pool.release(entry3, true);
339             pool.release(entry4, true);
340 
341             totals = pool.getTotalStats();
342             Assertions.assertEquals(2, totals.getAvailable());
343             Assertions.assertEquals(0, totals.getLeased());
344             Assertions.assertEquals(0, totals.getPending());
345 
346             final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", "some-other-stuff");
347 
348             Assertions.assertTrue(future5.isDone());
349 
350             Mockito.verify(conn2).close(CloseMode.GRACEFUL);
351             Mockito.verify(conn1, Mockito.never()).close(ArgumentMatchers.any());
352 
353             totals = pool.getTotalStats();
354             Assertions.assertEquals(1, totals.getAvailable());
355             Assertions.assertEquals(1, totals.getLeased());
356         }
357     }
358 
359     @Test
360     public void testCreateNewIfExpired() throws Exception {
361         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
362 
363         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2)) {
364 
365             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
366 
367             Assertions.assertTrue(future1.isDone());
368             final PoolEntry<String, HttpConnection> entry1 = future1.get();
369             Assertions.assertNotNull(entry1);
370             entry1.assignConnection(conn1);
371 
372             entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
373             pool.release(entry1, true);
374 
375             Thread.sleep(200L);
376 
377             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
378 
379             Assertions.assertTrue(future2.isDone());
380 
381             Mockito.verify(conn1).close(CloseMode.GRACEFUL);
382 
383             final PoolStats totals = pool.getTotalStats();
384             Assertions.assertEquals(0, totals.getAvailable());
385             Assertions.assertEquals(1, totals.getLeased());
386             Assertions.assertEquals(Collections.singleton("somehost"), pool.getRoutes());
387             final PoolStats stats = pool.getStats("somehost");
388             Assertions.assertEquals(0, stats.getAvailable());
389             Assertions.assertEquals(1, stats.getLeased());
390         }
391     }
392 
393     @Test
394     public void testCloseExpired() throws Exception {
395         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
396         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
397 
398         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2)) {
399 
400             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
401             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
402 
403             Assertions.assertTrue(future1.isDone());
404             final PoolEntry<String, HttpConnection> entry1 = future1.get();
405             Assertions.assertNotNull(entry1);
406             entry1.assignConnection(conn1);
407             Assertions.assertTrue(future2.isDone());
408             final PoolEntry<String, HttpConnection> entry2 = future2.get();
409             Assertions.assertNotNull(entry2);
410             entry2.assignConnection(conn2);
411 
412             entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
413             pool.release(entry1, true);
414 
415             Thread.sleep(200);
416 
417             entry2.updateExpiry(TimeValue.of(1000, TimeUnit.SECONDS));
418             pool.release(entry2, true);
419 
420             pool.closeExpired();
421 
422             Mockito.verify(conn1).close(CloseMode.GRACEFUL);
423             Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any());
424 
425             final PoolStats totals = pool.getTotalStats();
426             Assertions.assertEquals(1, totals.getAvailable());
427             Assertions.assertEquals(0, totals.getLeased());
428             Assertions.assertEquals(0, totals.getPending());
429             final PoolStats stats = pool.getStats("somehost");
430             Assertions.assertEquals(1, stats.getAvailable());
431             Assertions.assertEquals(0, stats.getLeased());
432             Assertions.assertEquals(0, stats.getPending());
433         }
434     }
435 
436     @Test
437     public void testCloseIdle() throws Exception {
438         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
439         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
440 
441         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2)) {
442 
443             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
444             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
445 
446             Assertions.assertTrue(future1.isDone());
447             final PoolEntry<String, HttpConnection> entry1 = future1.get();
448             Assertions.assertNotNull(entry1);
449             entry1.assignConnection(conn1);
450             Assertions.assertTrue(future2.isDone());
451             final PoolEntry<String, HttpConnection> entry2 = future2.get();
452             Assertions.assertNotNull(entry2);
453             entry2.assignConnection(conn2);
454 
455             entry1.updateState(null);
456             pool.release(entry1, true);
457 
458             Thread.sleep(200L);
459 
460             entry2.updateState(null);
461             pool.release(entry2, true);
462 
463             pool.closeIdle(TimeValue.of(50, TimeUnit.MILLISECONDS));
464 
465             Mockito.verify(conn1).close(CloseMode.GRACEFUL);
466             Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any());
467 
468             PoolStats totals = pool.getTotalStats();
469             Assertions.assertEquals(1, totals.getAvailable());
470             Assertions.assertEquals(0, totals.getLeased());
471             Assertions.assertEquals(0, totals.getPending());
472             PoolStats stats = pool.getStats("somehost");
473             Assertions.assertEquals(1, stats.getAvailable());
474             Assertions.assertEquals(0, stats.getLeased());
475             Assertions.assertEquals(0, stats.getPending());
476 
477             pool.closeIdle(TimeValue.of(-1, TimeUnit.MILLISECONDS));
478 
479             Mockito.verify(conn2).close(CloseMode.GRACEFUL);
480 
481             totals = pool.getTotalStats();
482             Assertions.assertEquals(0, totals.getAvailable());
483             Assertions.assertEquals(0, totals.getLeased());
484             Assertions.assertEquals(0, totals.getPending());
485             stats = pool.getStats("somehost");
486             Assertions.assertEquals(0, stats.getAvailable());
487             Assertions.assertEquals(0, stats.getLeased());
488             Assertions.assertEquals(0, stats.getPending());
489         }
490     }
491 
492     @Test
493     public void testLeaseRequestTimeout() throws Exception {
494         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
495 
496         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1)) {
497 
498             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null);
499             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null);
500             final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null);
501 
502             Assertions.assertTrue(future1.isDone());
503             final PoolEntry<String, HttpConnection> entry1 = future1.get();
504             Assertions.assertNotNull(entry1);
505             entry1.assignConnection(conn1);
506             Assertions.assertFalse(future2.isDone());
507             Assertions.assertFalse(future3.isDone());
508 
509             Thread.sleep(100);
510 
511             pool.validatePendingRequests();
512 
513             Assertions.assertFalse(future2.isDone());
514             Assertions.assertTrue(future3.isDone());
515         }
516     }
517 
518     private static class HoldInternalLockThread extends Thread {
519         private HoldInternalLockThread(final StrictConnPool<String, HttpConnection> pool, final CountDownLatch lockHeld) {
520             super(() -> {
521                 pool.lease("somehost", null); // lease a connection so we have something to enumLeased()
522                 pool.enumLeased(object -> {
523                     try {
524                         lockHeld.countDown();
525                         Thread.sleep(Long.MAX_VALUE);
526                     } catch (final InterruptedException ignored) {
527                     }
528                 });
529             });
530         }
531     }
532 
533     @Test
534     public void testLeaseRequestLockTimeout() throws Exception {
535         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1);
536         final CountDownLatch lockHeld = new CountDownLatch(1);
537         final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld);
538 
539         holdInternalLock.start(); // Start a thread to grab the internal conn pool lock
540         lockHeld.await(); // Wait until we know the internal lock is held
541 
542         // Attempt to get a connection while lock is held
543         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null);
544 
545         final ExecutionException executionException = Assertions.assertThrows(ExecutionException.class, () ->
546                 future2.get());
547         Assertions.assertTrue(executionException.getCause() instanceof DeadlineTimeoutException);
548         holdInternalLock.interrupt(); // Cleanup
549     }
550 
551     @Test
552     public void testLeaseRequestInterrupted() throws Exception {
553         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1);
554         final CountDownLatch lockHeld = new CountDownLatch(1);
555         final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld);
556 
557         holdInternalLock.start(); // Start a thread to grab the internal conn pool lock
558         lockHeld.await(); // Wait until we know the internal lock is held
559 
560         Thread.currentThread().interrupt();
561         // Attempt to get a connection while lock is held and thread is interrupted
562         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null);
563 
564         Assertions.assertTrue(Thread.interrupted());
565         Assertions.assertThrows(CancellationException.class, () -> future2.get());
566         holdInternalLock.interrupt(); // Cleanup
567     }
568 
569     @Test
570     public void testLeaseRequestCanceled() throws Exception {
571         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1)) {
572 
573             final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null,
574                     Timeout.ofMilliseconds(0), null);
575 
576             Assertions.assertTrue(future1.isDone());
577             final PoolEntry<String, HttpConnection> entry1 = future1.get();
578             Assertions.assertNotNull(entry1);
579             entry1.assignConnection(Mockito.mock(HttpConnection.class));
580 
581             final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null,
582                     Timeout.ofMilliseconds(0), null);
583             future2.cancel(true);
584 
585             pool.release(entry1, true);
586 
587             final PoolStats totals = pool.getTotalStats();
588             Assertions.assertEquals(1, totals.getAvailable());
589             Assertions.assertEquals(0, totals.getLeased());
590         }
591     }
592 
593     @Test
594     public void testGetStatsInvalid() throws Exception {
595         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2)) {
596             Assertions.assertThrows(NullPointerException.class, () -> pool.getStats(null));
597         }
598     }
599 
600     @Test
601     public void testSetMaxInvalid() throws Exception {
602         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2)) {
603             Assertions.assertThrows(IllegalArgumentException.class, () ->
604                     pool.setMaxTotal(-1));
605             Assertions.assertThrows(NullPointerException.class, () ->
606                     pool.setMaxPerRoute(null, 1));
607             Assertions.assertThrows(IllegalArgumentException.class, () ->
608                     pool.setDefaultMaxPerRoute(-1));
609     }
610     }
611 
612     @Test
613     public void testSetMaxPerRoute() throws Exception {
614         try (final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2)) {
615             pool.setMaxPerRoute("somehost", 1);
616             Assertions.assertEquals(1, pool.getMaxPerRoute("somehost"));
617             pool.setMaxPerRoute("somehost", 0);
618             Assertions.assertEquals(0, pool.getMaxPerRoute("somehost"));
619             pool.setMaxPerRoute("somehost", -1);
620             Assertions.assertEquals(2, pool.getMaxPerRoute("somehost"));
621         }
622     }
623 
624     @Test
625     public void testShutdown() throws Exception {
626         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
627         pool.close(CloseMode.GRACEFUL);
628         Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("somehost", null));
629         // Ignored if shut down
630         pool.release(new PoolEntry<>("somehost"), true);
631     }
632 
633 }