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          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      @Test
66      public void testInvalidConstruction() throws Exception {
67          Assertions.assertThrows(IllegalArgumentException.class, () ->
68                  new StrictConnPool<String, HttpConnection>(-1, 1));
69          Assertions.assertThrows(IllegalArgumentException.class, () ->
70                  new StrictConnPool<String, HttpConnection>(1, -1));
71      }
72  
73      @Test
74      public void testLeaseRelease() throws Exception {
75          final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
76          final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
77          final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
78  
79          final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10);
80          final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
81          final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
82          final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
83  
84          final PoolEntry<String, HttpConnection> entry1 = future1.get();
85          Assertions.assertNotNull(entry1);
86          entry1.assignConnection(conn1);
87          final PoolEntry<String, HttpConnection> entry2 = future2.get();
88          Assertions.assertNotNull(entry2);
89          entry2.assignConnection(conn2);
90          final PoolEntry<String, HttpConnection> entry3 = future3.get();
91          Assertions.assertNotNull(entry3);
92          entry3.assignConnection(conn3);
93  
94          pool.release(entry1, true);
95          pool.release(entry2, true);
96          pool.release(entry3, false);
97          Mockito.verify(conn1, Mockito.never()).close(ArgumentMatchers.any());
98          Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any());
99          Mockito.verify(conn3, Mockito.times(1)).close(CloseMode.GRACEFUL);
100 
101         final PoolStats totals = pool.getTotalStats();
102         Assertions.assertEquals(2, totals.getAvailable());
103         Assertions.assertEquals(0, totals.getLeased());
104         Assertions.assertEquals(0, totals.getPending());
105     }
106 
107     @Test
108     public void testLeaseInvalid() throws Exception {
109         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10);
110         Assertions.assertThrows(NullPointerException.class, () ->
111                 pool.lease(null, null, Timeout.ZERO_MILLISECONDS, null));
112         Assertions.assertThrows(NullPointerException.class, () ->
113                 pool.lease("somehost", null, null, null));
114     }
115 
116     @Test
117     public void testReleaseUnknownEntry() throws Exception {
118         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
119         Assertions.assertThrows(IllegalStateException.class, () ->
120                 pool.release(new PoolEntry<>("somehost"), true));
121     }
122 
123     @Test
124     public void testMaxLimits() throws Exception {
125         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
126         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
127         final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
128 
129         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10);
130         pool.setMaxPerRoute("somehost", 2);
131         pool.setMaxPerRoute("otherhost", 1);
132         pool.setMaxTotal(3);
133 
134         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
135         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
136         final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
137 
138         final PoolEntry<String, HttpConnection> entry1 = future1.get();
139         Assertions.assertNotNull(entry1);
140         entry1.assignConnection(conn1);
141         final PoolEntry<String, HttpConnection> entry2 = future2.get();
142         Assertions.assertNotNull(entry2);
143         entry2.assignConnection(conn2);
144         final PoolEntry<String, HttpConnection> entry3 = future3.get();
145         Assertions.assertNotNull(entry3);
146         entry3.assignConnection(conn3);
147 
148         pool.release(entry1, true);
149         pool.release(entry2, true);
150         pool.release(entry3, true);
151 
152         final PoolStats totals = pool.getTotalStats();
153         Assertions.assertEquals(3, totals.getAvailable());
154         Assertions.assertEquals(0, totals.getLeased());
155         Assertions.assertEquals(0, totals.getPending());
156 
157         final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("somehost", null);
158         final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", null);
159         final Future<PoolEntry<String, HttpConnection>> future6 = pool.lease("otherhost", null);
160         final Future<PoolEntry<String, HttpConnection>> future7 = pool.lease("somehost", null);
161         final Future<PoolEntry<String, HttpConnection>> future8 = pool.lease("somehost", null);
162         final Future<PoolEntry<String, HttpConnection>> future9 = pool.lease("otherhost", null);
163 
164         Assertions.assertTrue(future4.isDone());
165         final PoolEntry<String, HttpConnection> entry4 = future4.get();
166         Assertions.assertNotNull(entry4);
167         Assertions.assertSame(conn2, entry4.getConnection());
168 
169         Assertions.assertTrue(future5.isDone());
170         final PoolEntry<String, HttpConnection> entry5 = future5.get();
171         Assertions.assertNotNull(entry5);
172         Assertions.assertSame(conn1, entry5.getConnection());
173 
174         Assertions.assertTrue(future6.isDone());
175         final PoolEntry<String, HttpConnection> entry6 = future6.get();
176         Assertions.assertNotNull(entry6);
177         Assertions.assertSame(conn3, entry6.getConnection());
178 
179         Assertions.assertFalse(future7.isDone());
180         Assertions.assertFalse(future8.isDone());
181         Assertions.assertFalse(future9.isDone());
182 
183         pool.release(entry4, true);
184         pool.release(entry5, false);
185         pool.release(entry6, true);
186 
187         Assertions.assertTrue(future7.isDone());
188         final PoolEntry<String, HttpConnection> entry7 = future7.get();
189         Assertions.assertNotNull(entry7);
190         Assertions.assertSame(conn2, entry7.getConnection());
191 
192         Assertions.assertTrue(future8.isDone());
193         final PoolEntry<String, HttpConnection> entry8 = future8.get();
194         Assertions.assertNotNull(entry8);
195         Assertions.assertNull(entry8.getConnection());
196 
197         Assertions.assertTrue(future9.isDone());
198         final PoolEntry<String, HttpConnection> entry9 = future9.get();
199         Assertions.assertNotNull(entry9);
200         Assertions.assertSame(conn3, entry9.getConnection());
201     }
202 
203     @Test
204     public void testConnectionRedistributionOnTotalMaxLimit() throws Exception {
205         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
206         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
207         final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
208         final HttpConnection conn4 = Mockito.mock(HttpConnection.class);
209         final HttpConnection conn5 = Mockito.mock(HttpConnection.class);
210 
211         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10);
212         pool.setMaxPerRoute("somehost", 2);
213         pool.setMaxPerRoute("otherhost", 2);
214         pool.setMaxTotal(2);
215 
216         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
217         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
218         final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
219         final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("otherhost", null);
220 
221         Assertions.assertTrue(future1.isDone());
222         final PoolEntry<String, HttpConnection> entry1 = future1.get();
223         Assertions.assertNotNull(entry1);
224         Assertions.assertFalse(entry1.hasConnection());
225         entry1.assignConnection(conn1);
226         Assertions.assertTrue(future2.isDone());
227         final PoolEntry<String, HttpConnection> entry2 = future2.get();
228         Assertions.assertNotNull(entry2);
229         Assertions.assertFalse(entry2.hasConnection());
230         entry2.assignConnection(conn2);
231 
232         Assertions.assertFalse(future3.isDone());
233         Assertions.assertFalse(future4.isDone());
234 
235         PoolStats totals = pool.getTotalStats();
236         Assertions.assertEquals(0, totals.getAvailable());
237         Assertions.assertEquals(2, totals.getLeased());
238         Assertions.assertEquals(2, totals.getPending());
239 
240         pool.release(entry1, true);
241         pool.release(entry2, true);
242 
243         Assertions.assertTrue(future3.isDone());
244         final PoolEntry<String, HttpConnection> entry3 = future3.get();
245         Assertions.assertNotNull(entry3);
246         Assertions.assertFalse(entry3.hasConnection());
247         entry3.assignConnection(conn3);
248         Assertions.assertTrue(future4.isDone());
249         final PoolEntry<String, HttpConnection> entry4 = future4.get();
250         Assertions.assertNotNull(entry4);
251         Assertions.assertFalse(entry4.hasConnection());
252         entry4.assignConnection(conn4);
253 
254         totals = pool.getTotalStats();
255         Assertions.assertEquals(0, totals.getAvailable());
256         Assertions.assertEquals(2, totals.getLeased());
257         Assertions.assertEquals(0, totals.getPending());
258 
259         final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", null);
260         final Future<PoolEntry<String, HttpConnection>> future6 = pool.lease("otherhost", null);
261 
262         pool.release(entry3, true);
263         pool.release(entry4, true);
264 
265         Assertions.assertTrue(future5.isDone());
266         final PoolEntry<String, HttpConnection> entry5 = future5.get();
267         Assertions.assertNotNull(entry5);
268         Assertions.assertFalse(entry5.hasConnection());
269         entry5.assignConnection(conn5);
270         Assertions.assertTrue(future6.isDone());
271         final PoolEntry<String, HttpConnection> entry6 = future6.get();
272         Assertions.assertNotNull(entry6);
273         Assertions.assertTrue(entry6.hasConnection());
274         Assertions.assertSame(conn4, entry6.getConnection());
275 
276         totals = pool.getTotalStats();
277         Assertions.assertEquals(0, totals.getAvailable());
278         Assertions.assertEquals(2, totals.getLeased());
279         Assertions.assertEquals(0, totals.getPending());
280 
281         pool.release(entry5, true);
282         pool.release(entry6, true);
283 
284         totals = pool.getTotalStats();
285         Assertions.assertEquals(2, totals.getAvailable());
286         Assertions.assertEquals(0, totals.getLeased());
287         Assertions.assertEquals(0, totals.getPending());
288     }
289 
290     @Test
291     public void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exception {
292         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
293         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
294 
295         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 10);
296         pool.setMaxPerRoute("somehost", 2);
297         pool.setMaxTotal(2);
298 
299         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
300         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
301 
302         Assertions.assertTrue(future1.isDone());
303         final PoolEntry<String, HttpConnection> entry1 = future1.get();
304         entry1.assignConnection(conn1);
305         Assertions.assertNotNull(entry1);
306         Assertions.assertTrue(future2.isDone());
307         final PoolEntry<String, HttpConnection> entry2 = future2.get();
308         Assertions.assertNotNull(entry2);
309         entry2.assignConnection(conn2);
310 
311         PoolStats totals = pool.getTotalStats();
312         Assertions.assertEquals(0, totals.getAvailable());
313         Assertions.assertEquals(2, totals.getLeased());
314         Assertions.assertEquals(0, totals.getPending());
315 
316         entry1.updateState("some-stuff");
317         pool.release(entry1, true);
318         entry2.updateState("some-stuff");
319         pool.release(entry2, true);
320 
321         final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("somehost", "some-stuff");
322         final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("somehost", "some-stuff");
323 
324         Assertions.assertTrue(future1.isDone());
325         final PoolEntry<String, HttpConnection> entry3 = future3.get();
326         Assertions.assertNotNull(entry3);
327         Assertions.assertSame(conn2, entry3.getConnection());
328         Assertions.assertTrue(future4.isDone());
329         final PoolEntry<String, HttpConnection> entry4 = future4.get();
330         Assertions.assertNotNull(entry4);
331         Assertions.assertSame(conn1, entry4.getConnection());
332 
333         pool.release(entry3, true);
334         pool.release(entry4, true);
335 
336         totals = pool.getTotalStats();
337         Assertions.assertEquals(2, totals.getAvailable());
338         Assertions.assertEquals(0, totals.getLeased());
339         Assertions.assertEquals(0, totals.getPending());
340 
341         final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", "some-other-stuff");
342 
343         Assertions.assertTrue(future5.isDone());
344 
345         Mockito.verify(conn2).close(CloseMode.GRACEFUL);
346         Mockito.verify(conn1, Mockito.never()).close(ArgumentMatchers.any());
347 
348         totals = pool.getTotalStats();
349         Assertions.assertEquals(1, totals.getAvailable());
350         Assertions.assertEquals(1, totals.getLeased());
351     }
352 
353     @Test
354     public void testCreateNewIfExpired() throws Exception {
355         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
356 
357         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
358 
359         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
360 
361         Assertions.assertTrue(future1.isDone());
362         final PoolEntry<String, HttpConnection> entry1 = future1.get();
363         Assertions.assertNotNull(entry1);
364         entry1.assignConnection(conn1);
365 
366         entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
367         pool.release(entry1, true);
368 
369         Thread.sleep(200L);
370 
371         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
372 
373         Assertions.assertTrue(future2.isDone());
374 
375         Mockito.verify(conn1).close(CloseMode.GRACEFUL);
376 
377         final PoolStats totals = pool.getTotalStats();
378         Assertions.assertEquals(0, totals.getAvailable());
379         Assertions.assertEquals(1, totals.getLeased());
380         Assertions.assertEquals(Collections.singleton("somehost"), pool.getRoutes());
381         final PoolStats stats = pool.getStats("somehost");
382         Assertions.assertEquals(0, stats.getAvailable());
383         Assertions.assertEquals(1, stats.getLeased());
384     }
385 
386     @Test
387     public void testCloseExpired() throws Exception {
388         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
389         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
390 
391         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
392 
393         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
394         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
395 
396         Assertions.assertTrue(future1.isDone());
397         final PoolEntry<String, HttpConnection> entry1 = future1.get();
398         Assertions.assertNotNull(entry1);
399         entry1.assignConnection(conn1);
400         Assertions.assertTrue(future2.isDone());
401         final PoolEntry<String, HttpConnection> entry2 = future2.get();
402         Assertions.assertNotNull(entry2);
403         entry2.assignConnection(conn2);
404 
405         entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
406         pool.release(entry1, true);
407 
408         Thread.sleep(200);
409 
410         entry2.updateExpiry(TimeValue.of(1000, TimeUnit.SECONDS));
411         pool.release(entry2, true);
412 
413         pool.closeExpired();
414 
415         Mockito.verify(conn1).close(CloseMode.GRACEFUL);
416         Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any());
417 
418         final PoolStats totals = pool.getTotalStats();
419         Assertions.assertEquals(1, totals.getAvailable());
420         Assertions.assertEquals(0, totals.getLeased());
421         Assertions.assertEquals(0, totals.getPending());
422         final PoolStats stats = pool.getStats("somehost");
423         Assertions.assertEquals(1, stats.getAvailable());
424         Assertions.assertEquals(0, stats.getLeased());
425         Assertions.assertEquals(0, stats.getPending());
426     }
427 
428     @Test
429     public void testCloseIdle() throws Exception {
430         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
431         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
432 
433         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
434 
435         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
436         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
437 
438         Assertions.assertTrue(future1.isDone());
439         final PoolEntry<String, HttpConnection> entry1 = future1.get();
440         Assertions.assertNotNull(entry1);
441         entry1.assignConnection(conn1);
442         Assertions.assertTrue(future2.isDone());
443         final PoolEntry<String, HttpConnection> entry2 = future2.get();
444         Assertions.assertNotNull(entry2);
445         entry2.assignConnection(conn2);
446 
447         entry1.updateState(null);
448         pool.release(entry1, true);
449 
450         Thread.sleep(200L);
451 
452         entry2.updateState(null);
453         pool.release(entry2, true);
454 
455         pool.closeIdle(TimeValue.of(50, TimeUnit.MILLISECONDS));
456 
457         Mockito.verify(conn1).close(CloseMode.GRACEFUL);
458         Mockito.verify(conn2, Mockito.never()).close(ArgumentMatchers.any());
459 
460         PoolStats totals = pool.getTotalStats();
461         Assertions.assertEquals(1, totals.getAvailable());
462         Assertions.assertEquals(0, totals.getLeased());
463         Assertions.assertEquals(0, totals.getPending());
464         PoolStats stats = pool.getStats("somehost");
465         Assertions.assertEquals(1, stats.getAvailable());
466         Assertions.assertEquals(0, stats.getLeased());
467         Assertions.assertEquals(0, stats.getPending());
468 
469         pool.closeIdle(TimeValue.of(-1, TimeUnit.MILLISECONDS));
470 
471         Mockito.verify(conn2).close(CloseMode.GRACEFUL);
472 
473         totals = pool.getTotalStats();
474         Assertions.assertEquals(0, totals.getAvailable());
475         Assertions.assertEquals(0, totals.getLeased());
476         Assertions.assertEquals(0, totals.getPending());
477         stats = pool.getStats("somehost");
478         Assertions.assertEquals(0, stats.getAvailable());
479         Assertions.assertEquals(0, stats.getLeased());
480         Assertions.assertEquals(0, stats.getPending());
481     }
482 
483     @Test
484     public void testLeaseRequestTimeout() throws Exception {
485         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
486 
487         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1);
488 
489         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null);
490         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null);
491         final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null);
492 
493         Assertions.assertTrue(future1.isDone());
494         final PoolEntry<String, HttpConnection> entry1 = future1.get();
495         Assertions.assertNotNull(entry1);
496         entry1.assignConnection(conn1);
497         Assertions.assertFalse(future2.isDone());
498         Assertions.assertFalse(future3.isDone());
499 
500         Thread.sleep(100);
501 
502         pool.validatePendingRequests();
503 
504         Assertions.assertFalse(future2.isDone());
505         Assertions.assertTrue(future3.isDone());
506     }
507 
508     private static class HoldInternalLockThread extends Thread {
509         private HoldInternalLockThread(final StrictConnPool<String, HttpConnection> pool, final CountDownLatch lockHeld) {
510             super(() -> {
511                 pool.lease("somehost", null); // lease a connection so we have something to enumLeased()
512                 pool.enumLeased(object -> {
513                     try {
514                         lockHeld.countDown();
515                         Thread.sleep(Long.MAX_VALUE);
516                     } catch (final InterruptedException ignored) {
517                     }
518                 });
519             });
520         }
521     }
522 
523     @Test
524     public void testLeaseRequestLockTimeout() throws Exception {
525         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1);
526         final CountDownLatch lockHeld = new CountDownLatch(1);
527         final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld);
528 
529         holdInternalLock.start(); // Start a thread to grab the internal conn pool lock
530         lockHeld.await(); // Wait until we know the internal lock is held
531 
532         // Attempt to get a connection while lock is held
533         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null);
534 
535         final ExecutionException executionException = Assertions.assertThrows(ExecutionException.class, () ->
536                 future2.get());
537         Assertions.assertTrue(executionException.getCause() instanceof DeadlineTimeoutException);
538         holdInternalLock.interrupt(); // Cleanup
539     }
540 
541     @Test
542     public void testLeaseRequestInterrupted() throws Exception {
543         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1);
544         final CountDownLatch lockHeld = new CountDownLatch(1);
545         final Thread holdInternalLock = new HoldInternalLockThread(pool, lockHeld);
546 
547         holdInternalLock.start(); // Start a thread to grab the internal conn pool lock
548         lockHeld.await(); // Wait until we know the internal lock is held
549 
550         Thread.currentThread().interrupt();
551         // Attempt to get a connection while lock is held and thread is interrupted
552         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(10), null);
553 
554         Assertions.assertTrue(Thread.interrupted());
555         Assertions.assertThrows(CancellationException.class, () -> future2.get());
556         holdInternalLock.interrupt(); // Cleanup
557     }
558 
559     @Test
560     public void testLeaseRequestCanceled() throws Exception {
561         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(1, 1);
562 
563         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null);
564 
565         Assertions.assertTrue(future1.isDone());
566         final PoolEntry<String, HttpConnection> entry1 = future1.get();
567         Assertions.assertNotNull(entry1);
568         entry1.assignConnection(Mockito.mock(HttpConnection.class));
569 
570         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMilliseconds(0), null);
571         future2.cancel(true);
572 
573         pool.release(entry1, true);
574 
575         final PoolStats totals = pool.getTotalStats();
576         Assertions.assertEquals(1, totals.getAvailable());
577         Assertions.assertEquals(0, totals.getLeased());
578     }
579 
580     @Test
581     public void testGetStatsInvalid() throws Exception {
582         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
583         Assertions.assertThrows(NullPointerException.class, () ->
584                 pool.getStats(null));
585     }
586 
587     @Test
588     public void testSetMaxInvalid() throws Exception {
589         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
590         Assertions.assertThrows(IllegalArgumentException.class, () ->
591                 pool.setMaxTotal(-1));
592         Assertions.assertThrows(NullPointerException.class, () ->
593                 pool.setMaxPerRoute(null, 1));
594         Assertions.assertThrows(IllegalArgumentException.class, () ->
595                 pool.setDefaultMaxPerRoute(-1));
596     }
597 
598     @Test
599     public void testSetMaxPerRoute() throws Exception {
600         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
601         pool.setMaxPerRoute("somehost", 1);
602         Assertions.assertEquals(1, pool.getMaxPerRoute("somehost"));
603         pool.setMaxPerRoute("somehost", 0);
604         Assertions.assertEquals(0, pool.getMaxPerRoute("somehost"));
605         pool.setMaxPerRoute("somehost", -1);
606         Assertions.assertEquals(2, pool.getMaxPerRoute("somehost"));
607     }
608 
609     @Test
610     public void testShutdown() throws Exception {
611         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
612         pool.close(CloseMode.GRACEFUL);
613         Assertions.assertThrows(IllegalStateException.class, () -> pool.lease("somehost", null));
614         // Ignored if shut down
615         pool.release(new PoolEntry<>("somehost"), true);
616     }
617 
618 }