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