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.http.pool;
28  
29  import java.io.IOException;
30  import java.util.Collections;
31  import java.util.concurrent.ExecutionException;
32  import java.util.concurrent.Future;
33  import java.util.concurrent.TimeUnit;
34  import java.util.concurrent.TimeoutException;
35  
36  import org.apache.http.HttpConnection;
37  import org.junit.Assert;
38  import org.junit.Test;
39  import org.mockito.Mockito;
40  
41  public class TestConnPool {
42  
43      private static final int GRACE_PERIOD = 10000;
44  
45      static interface LocalConnFactory extends ConnFactory<String, HttpConnection> {
46      }
47  
48      static class LocalPoolEntry extends PoolEntry<String, HttpConnection> {
49  
50          private boolean closed;
51  
52          public LocalPoolEntry(final String route, final HttpConnection conn) {
53              super(null, route, conn);
54          }
55  
56          @Override
57          public void close() {
58              if (this.closed) {
59                  return;
60              }
61              this.closed = true;
62              try {
63                  getConnection().close();
64              } catch (final IOException ignore) {
65              }
66          }
67  
68          @Override
69          public boolean isClosed() {
70              return this.closed;
71          }
72  
73      }
74  
75      static class LocalConnPool extends AbstractConnPool<String, HttpConnection, LocalPoolEntry> {
76  
77          public LocalConnPool(
78                  final ConnFactory<String, HttpConnection> connFactory,
79                  final int defaultMaxPerRoute, final int maxTotal) {
80              super(connFactory, defaultMaxPerRoute, maxTotal);
81          }
82  
83          @Override
84          protected LocalPoolEntry createEntry(final String route, final HttpConnection conn) {
85              return new LocalPoolEntry(route, conn);
86          }
87  
88          @Override
89          protected boolean validate(final LocalPoolEntry entry) {
90              return !entry.getConnection().isStale();
91          }
92      }
93  
94      @Test
95      public void testEmptyPool() throws Exception {
96          final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
97          final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
98          pool.setDefaultMaxPerRoute(5);
99          pool.setMaxPerRoute("somehost", 3);
100         final PoolStats totals = pool.getTotalStats();
101         Assert.assertEquals(0, totals.getAvailable());
102         Assert.assertEquals(0, totals.getLeased());
103         Assert.assertEquals(10, totals.getMax());
104         Assert.assertEquals(Collections.emptySet(), pool.getRoutes());
105         final PoolStats stats = pool.getStats("somehost");
106         Assert.assertEquals(0, stats.getAvailable());
107         Assert.assertEquals(0, stats.getLeased());
108         Assert.assertEquals(3, stats.getMax());
109         Assert.assertEquals("[leased: []][available: []][pending: []]", pool.toString());
110     }
111 
112     @Test
113     public void testInvalidConstruction() throws Exception {
114         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
115         try {
116             new LocalConnPool(connFactory, -1, 1);
117             Assert.fail("IllegalArgumentException should have been thrown");
118         } catch (final IllegalArgumentException expected) {
119         }
120         try {
121             new LocalConnPool(connFactory, 1, -1);
122             Assert.fail("IllegalArgumentException should have been thrown");
123         } catch (final IllegalArgumentException expected) {
124         }
125     }
126 
127     @Test
128     public void testLeaseRelease() throws Exception {
129         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
130         Mockito.when(conn1.isOpen()).thenReturn(true);
131         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
132         Mockito.when(conn2.isOpen()).thenReturn(true);
133 
134         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
135         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
136         Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);
137 
138         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
139         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
140         final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
141         Assert.assertNotNull(entry1);
142         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
143         final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
144         Assert.assertNotNull(entry2);
145         final Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
146         final LocalPoolEntry entry3 = future3.get(1, TimeUnit.SECONDS);
147         Assert.assertNotNull(entry3);
148 
149         PoolStats totals = pool.getTotalStats();
150         Assert.assertEquals(0, totals.getAvailable());
151         Assert.assertEquals(3, totals.getLeased());
152 
153         final LocalPoolEntry entry = future1.get();
154         Assert.assertSame(entry1, entry);
155 
156         pool.release(entry1, true);
157         pool.release(entry2, true);
158         pool.release(entry3, false);
159         Mockito.verify(conn1, Mockito.never()).close();
160         Mockito.verify(conn2, Mockito.times(1)).close();
161 
162         totals = pool.getTotalStats();
163         Assert.assertEquals(2, totals.getAvailable());
164         Assert.assertEquals(0, totals.getLeased());
165     }
166 
167     @Test
168     public void testLeaseIllegal() throws Exception {
169         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
170         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
171         try {
172             pool.lease(null, null);
173             Assert.fail("IllegalArgumentException should have been thrown");
174         } catch (final IllegalArgumentException expected) {
175         }
176     }
177 
178     @Test
179     public void testReleaseUnknownEntry() throws Exception {
180         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
181         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
182         pool.release(new LocalPoolEntry("somehost", Mockito.mock(HttpConnection.class)), true);
183     }
184 
185     static class GetPoolEntryThread extends Thread {
186 
187         private final Future<LocalPoolEntry> future;
188         private final long time;
189         private final TimeUnit tunit;
190 
191         private volatile LocalPoolEntry entry;
192         private volatile Exception ex;
193 
194         GetPoolEntryThread(final Future<LocalPoolEntry> future, final long time, final TimeUnit tunit) {
195             super();
196             this.future = future;
197             this.time = time;
198             this.tunit = tunit;
199             setDaemon(true);
200         }
201 
202         GetPoolEntryThread(final Future<LocalPoolEntry> future) {
203             this(future, 1000, TimeUnit.SECONDS);
204         }
205 
206         @Override
207         public void run() {
208             try {
209                 this.entry = this.future.get(this.time, this.tunit);
210             } catch (final Exception ex) {
211                 this.ex = ex;
212             }
213         }
214 
215         public boolean isDone() {
216             return this.future.isDone();
217         }
218 
219         public LocalPoolEntry getEntry() {
220             return this.entry;
221         }
222 
223         public Exception getException() {
224             return this.ex;
225         }
226 
227     }
228 
229     @Test
230     public void testMaxLimits() throws Exception {
231         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
232 
233         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
234         Mockito.when(conn1.isOpen()).thenReturn(true);
235         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
236 
237         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
238         Mockito.when(conn2.isOpen()).thenReturn(true);
239         Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);
240 
241         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
242         pool.setMaxPerRoute("somehost", 2);
243         pool.setMaxPerRoute("otherhost", 1);
244         pool.setMaxTotal(3);
245 
246         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
247         final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
248         t1.start();
249         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
250         final GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
251         t2.start();
252         final Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
253         final GetPoolEntryThread t3 = new GetPoolEntryThread(future3);
254         t3.start();
255 
256         t1.join(GRACE_PERIOD);
257         Assert.assertTrue(future1.isDone());
258         final LocalPoolEntry entry1 = t1.getEntry();
259         Assert.assertNotNull(entry1);
260         t2.join(GRACE_PERIOD);
261         Assert.assertTrue(future2.isDone());
262         final LocalPoolEntry entry2 = t2.getEntry();
263         Assert.assertNotNull(entry2);
264         t3.join(GRACE_PERIOD);
265         Assert.assertTrue(future3.isDone());
266         final LocalPoolEntry entry3 = t3.getEntry();
267         Assert.assertNotNull(entry3);
268 
269         pool.release(entry1, true);
270         pool.release(entry2, true);
271         pool.release(entry3, true);
272 
273         final PoolStats totals = pool.getTotalStats();
274         Assert.assertEquals(3, totals.getAvailable());
275         Assert.assertEquals(0, totals.getLeased());
276 
277         final Future<LocalPoolEntry> future4 = pool.lease("somehost", null);
278         final GetPoolEntryThread t4 = new GetPoolEntryThread(future4);
279         t4.start();
280         final Future<LocalPoolEntry> future5 = pool.lease("somehost", null);
281         final GetPoolEntryThread t5 = new GetPoolEntryThread(future5);
282         t5.start();
283         final Future<LocalPoolEntry> future6 = pool.lease("otherhost", null);
284         final GetPoolEntryThread t6 = new GetPoolEntryThread(future6);
285         t6.start();
286 
287         t4.join(GRACE_PERIOD);
288         Assert.assertTrue(future4.isDone());
289         final LocalPoolEntry entry4 = t4.getEntry();
290         Assert.assertNotNull(entry4);
291         t5.join(GRACE_PERIOD);
292         Assert.assertTrue(future5.isDone());
293         final LocalPoolEntry entry5 = t5.getEntry();
294         Assert.assertNotNull(entry5);
295         t6.join(GRACE_PERIOD);
296         Assert.assertTrue(future6.isDone());
297         final LocalPoolEntry entry6 = t6.getEntry();
298         Assert.assertNotNull(entry6);
299 
300         final Future<LocalPoolEntry> future7 = pool.lease("somehost", null);
301         final GetPoolEntryThread t7 = new GetPoolEntryThread(future7);
302         t7.start();
303         final Future<LocalPoolEntry> future8 = pool.lease("somehost", null);
304         final GetPoolEntryThread t8 = new GetPoolEntryThread(future8);
305         t8.start();
306         final Future<LocalPoolEntry> future9 = pool.lease("otherhost", null);
307         final GetPoolEntryThread t9 = new GetPoolEntryThread(future9);
308         t9.start();
309 
310         Assert.assertFalse(t7.isDone());
311         Assert.assertFalse(t8.isDone());
312         Assert.assertFalse(t9.isDone());
313 
314         Mockito.verify(connFactory, Mockito.times(3)).create(Mockito.any(String.class));
315 
316         pool.release(entry4, true);
317         pool.release(entry5, false);
318         pool.release(entry6, true);
319 
320         t7.join();
321         Assert.assertTrue(future7.isDone());
322         t8.join();
323         Assert.assertTrue(future8.isDone());
324         t9.join();
325         Assert.assertTrue(future9.isDone());
326 
327         Mockito.verify(connFactory, Mockito.times(4)).create(Mockito.any(String.class));
328     }
329 
330     @Test
331     public void testConnectionRedistributionOnTotalMaxLimit() throws Exception {
332         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
333 
334         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
335         Mockito.when(conn1.isOpen()).thenReturn(true);
336         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
337         Mockito.when(conn2.isOpen()).thenReturn(true);
338         final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
339         Mockito.when(conn3.isOpen()).thenReturn(true);
340         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2, conn3);
341 
342         final HttpConnection conn4 = Mockito.mock(HttpConnection.class);
343         Mockito.when(conn4.isOpen()).thenReturn(true);
344         final HttpConnection conn5 = Mockito.mock(HttpConnection.class);
345         Mockito.when(conn5.isOpen()).thenReturn(true);
346         Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn4, conn5);
347 
348         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
349         pool.setMaxPerRoute("somehost", 2);
350         pool.setMaxPerRoute("otherhost", 2);
351         pool.setMaxTotal(2);
352 
353         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
354         final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
355         t1.start();
356         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
357         final GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
358         t2.start();
359 
360         t1.join(GRACE_PERIOD);
361         Assert.assertTrue(future1.isDone());
362         final LocalPoolEntry entry1 = t1.getEntry();
363         Assert.assertNotNull(entry1);
364         t2.join(GRACE_PERIOD);
365         Assert.assertTrue(future2.isDone());
366         final LocalPoolEntry entry2 = t2.getEntry();
367         Assert.assertNotNull(entry2);
368 
369         final Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
370         final GetPoolEntryThread t3 = new GetPoolEntryThread(future3);
371         t3.start();
372         final Future<LocalPoolEntry> future4 = pool.lease("otherhost", null);
373         final GetPoolEntryThread t4 = new GetPoolEntryThread(future4);
374         t4.start();
375 
376         Assert.assertFalse(t3.isDone());
377         Assert.assertFalse(t4.isDone());
378 
379         Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
380         Mockito.verify(connFactory, Mockito.never()).create(Mockito.eq("otherhost"));
381 
382         PoolStats totals = pool.getTotalStats();
383         Assert.assertEquals(0, totals.getAvailable());
384         Assert.assertEquals(2, totals.getLeased());
385 
386         pool.release(entry1, true);
387         pool.release(entry2, true);
388 
389         t3.join(GRACE_PERIOD);
390         Assert.assertTrue(future3.isDone());
391         final LocalPoolEntry entry3 = t3.getEntry();
392         Assert.assertNotNull(entry3);
393         t4.join(GRACE_PERIOD);
394         Assert.assertTrue(future4.isDone());
395         final LocalPoolEntry entry4 = t4.getEntry();
396         Assert.assertNotNull(entry4);
397 
398         Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
399         Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("otherhost"));
400 
401         totals = pool.getTotalStats();
402         Assert.assertEquals(0, totals.getAvailable());
403         Assert.assertEquals(2, totals.getLeased());
404 
405         final Future<LocalPoolEntry> future5 = pool.lease("somehost", null);
406         final GetPoolEntryThread t5 = new GetPoolEntryThread(future5);
407         t5.start();
408         final Future<LocalPoolEntry> future6 = pool.lease("otherhost", null);
409         final GetPoolEntryThread t6 = new GetPoolEntryThread(future6);
410         t6.start();
411 
412         pool.release(entry3, true);
413         pool.release(entry4, true);
414 
415         t5.join(GRACE_PERIOD);
416         Assert.assertTrue(future5.isDone());
417         final LocalPoolEntry entry5 = t5.getEntry();
418         Assert.assertNotNull(entry5);
419         t6.join(GRACE_PERIOD);
420         Assert.assertTrue(future6.isDone());
421         final LocalPoolEntry entry6 = t6.getEntry();
422         Assert.assertNotNull(entry6);
423 
424         Mockito.verify(connFactory, Mockito.times(3)).create(Mockito.eq("somehost"));
425         Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("otherhost"));
426 
427         totals = pool.getTotalStats();
428         Assert.assertEquals(0, totals.getAvailable());
429         Assert.assertEquals(2, totals.getLeased());
430 
431         pool.release(entry5, true);
432         pool.release(entry6, true);
433 
434         totals = pool.getTotalStats();
435         Assert.assertEquals(2, totals.getAvailable());
436         Assert.assertEquals(0, totals.getLeased());
437     }
438 
439     @Test
440     public void testStatefulConnectionRedistributionOnPerRouteMaxLimit() throws Exception {
441         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
442 
443         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
444         Mockito.when(conn1.isOpen()).thenReturn(true);
445         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
446         Mockito.when(conn2.isOpen()).thenReturn(true);
447         final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
448         Mockito.when(conn3.isOpen()).thenReturn(true);
449         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2, conn3);
450 
451         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
452         pool.setMaxPerRoute("somehost", 2);
453         pool.setMaxTotal(2);
454 
455         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
456         final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
457         t1.start();
458 
459         t1.join(GRACE_PERIOD);
460         Assert.assertTrue(future1.isDone());
461         final LocalPoolEntry entry1 = t1.getEntry();
462         Assert.assertNotNull(entry1);
463 
464         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
465         final GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
466         t2.start();
467 
468         t2.join(GRACE_PERIOD);
469         Assert.assertTrue(future2.isDone());
470         final LocalPoolEntry entry2 = t2.getEntry();
471         Assert.assertNotNull(entry2);
472 
473         PoolStats totals = pool.getTotalStats();
474         Assert.assertEquals(0, totals.getAvailable());
475         Assert.assertEquals(2, totals.getLeased());
476         Assert.assertEquals(0, totals.getPending());
477 
478         entry1.setState("some-stuff");
479         pool.release(entry1, true);
480         entry2.setState("some-stuff");
481         pool.release(entry2, true);
482 
483         Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
484 
485         final Future<LocalPoolEntry> future3 = pool.lease("somehost", "some-other-stuff");
486         final GetPoolEntryThread t3 = new GetPoolEntryThread(future3);
487         t3.start();
488 
489         t3.join(GRACE_PERIOD);
490         Assert.assertTrue(future3.isDone());
491         final LocalPoolEntry entry3 = t3.getEntry();
492         Assert.assertNotNull(entry3);
493 
494         Mockito.verify(connFactory, Mockito.times(3)).create(Mockito.eq("somehost"));
495 
496         Mockito.verify(conn1).close();
497         Mockito.verify(conn2, Mockito.never()).close();
498 
499         totals = pool.getTotalStats();
500         Assert.assertEquals(1, totals.getAvailable());
501         Assert.assertEquals(1, totals.getLeased());
502 
503     }
504 
505     @Test
506     public void testCreateNewIfExpired() throws Exception {
507         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
508 
509         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
510         Mockito.when(conn1.isOpen()).thenReturn(true);
511         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
512 
513         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
514 
515         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
516         final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
517         Assert.assertNotNull(entry1);
518 
519         Mockito.verify(connFactory, Mockito.times(1)).create(Mockito.eq("somehost"));
520 
521         entry1.updateExpiry(1, TimeUnit.MILLISECONDS);
522         pool.release(entry1, true);
523 
524         Thread.sleep(200L);
525 
526         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
527         final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
528         Assert.assertNotNull(entry2);
529 
530         Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
531 
532         final PoolStats totals = pool.getTotalStats();
533         Assert.assertEquals(0, totals.getAvailable());
534         Assert.assertEquals(1, totals.getLeased());
535         Assert.assertEquals(Collections.singleton("somehost"), pool.getRoutes());
536         final PoolStats stats = pool.getStats("somehost");
537         Assert.assertEquals(0, stats.getAvailable());
538         Assert.assertEquals(1, stats.getLeased());
539     }
540 
541     @Test
542     public void testCloseExpired() throws Exception {
543         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
544 
545         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
546         Mockito.when(conn1.isOpen()).thenReturn(Boolean.FALSE);
547         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
548         Mockito.when(conn2.isOpen()).thenReturn(Boolean.TRUE);
549 
550         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2);
551 
552         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
553 
554         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
555         final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
556         Assert.assertNotNull(entry1);
557         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
558         final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
559         Assert.assertNotNull(entry2);
560 
561         entry1.updateExpiry(1, TimeUnit.MILLISECONDS);
562         pool.release(entry1, true);
563 
564         Thread.sleep(200);
565 
566         entry2.updateExpiry(1000, TimeUnit.SECONDS);
567         pool.release(entry2, true);
568 
569         pool.closeExpired();
570 
571         Mockito.verify(conn1).close();
572         Mockito.verify(conn2, Mockito.never()).close();
573 
574         final PoolStats totals = pool.getTotalStats();
575         Assert.assertEquals(1, totals.getAvailable());
576         Assert.assertEquals(0, totals.getLeased());
577         final PoolStats stats = pool.getStats("somehost");
578         Assert.assertEquals(1, stats.getAvailable());
579         Assert.assertEquals(0, stats.getLeased());
580     }
581 
582     @Test
583     public void testLeaseTimeout() throws Exception {
584         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
585 
586         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
587         Mockito.when(conn1.isOpen()).thenReturn(true);
588         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
589 
590         final LocalConnPool pool = new LocalConnPool(connFactory, 1, 1);
591 
592         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
593         final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
594         t1.start();
595 
596         t1.join(GRACE_PERIOD);
597         Assert.assertTrue(future1.isDone());
598         final LocalPoolEntry entry1 = t1.getEntry();
599         Assert.assertNotNull(entry1);
600 
601         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
602         final GetPoolEntryThread t2 = new GetPoolEntryThread(future2, 50, TimeUnit.MICROSECONDS);
603         t2.start();
604 
605         t2.join(GRACE_PERIOD);
606         Assert.assertTrue(t2.getException() instanceof TimeoutException);
607         Assert.assertFalse(future2.isDone());
608         Assert.assertFalse(future2.isCancelled());
609     }
610 
611     @Test
612     public void testLeaseIOException() throws Exception {
613         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
614         Mockito.doThrow(new IOException("Oppsie")).when(connFactory).create("somehost");
615 
616         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
617 
618         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
619         final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
620         t1.start();
621 
622         t1.join(GRACE_PERIOD);
623         Assert.assertTrue(future1.isDone());
624         Assert.assertTrue(t1.getException() instanceof ExecutionException);
625         Assert.assertTrue(t1.getException().getCause() instanceof IOException);
626         Assert.assertFalse(future1.isCancelled());
627     }
628 
629     @Test
630     public void testLeaseCancel() throws Exception {
631         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
632 
633         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
634         Mockito.when(conn1.isOpen()).thenReturn(true);
635         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
636 
637         final LocalConnPool pool = new LocalConnPool(connFactory, 1, 1);
638 
639         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
640         final GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
641         t1.start();
642 
643         t1.join(GRACE_PERIOD);
644         Assert.assertTrue(future1.isDone());
645         final LocalPoolEntry entry1 = t1.getEntry();
646         Assert.assertNotNull(entry1);
647 
648         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
649         final GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
650         t2.start();
651 
652         Thread.sleep(5);
653 
654         Assert.assertFalse(future2.isDone());
655         Assert.assertFalse(future2.isCancelled());
656 
657         future2.cancel(true);
658         t2.join(GRACE_PERIOD);
659         Assert.assertTrue(future2.isDone());
660         Assert.assertTrue(future2.isCancelled());
661         future2.cancel(true);
662         future2.cancel(true);
663     }
664 
665     @Test
666     public void testCloseIdle() throws Exception {
667         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
668 
669         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
670         Mockito.when(conn1.isOpen()).thenReturn(true);
671         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
672         Mockito.when(conn2.isOpen()).thenReturn(true);
673 
674         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2);
675 
676         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
677 
678         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
679         final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
680         Assert.assertNotNull(entry1);
681         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
682         final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
683         Assert.assertNotNull(entry2);
684 
685         entry1.updateExpiry(0, TimeUnit.MILLISECONDS);
686         pool.release(entry1, true);
687 
688         Thread.sleep(200L);
689 
690         entry2.updateExpiry(0, TimeUnit.MILLISECONDS);
691         pool.release(entry2, true);
692 
693         pool.closeIdle(50, TimeUnit.MILLISECONDS);
694 
695         Mockito.verify(conn1).close();
696         Mockito.verify(conn2, Mockito.never()).close();
697 
698         PoolStats totals = pool.getTotalStats();
699         Assert.assertEquals(1, totals.getAvailable());
700         Assert.assertEquals(0, totals.getLeased());
701         PoolStats stats = pool.getStats("somehost");
702         Assert.assertEquals(1, stats.getAvailable());
703         Assert.assertEquals(0, stats.getLeased());
704 
705         pool.closeIdle(-1, TimeUnit.MILLISECONDS);
706 
707         Mockito.verify(conn2).close();
708 
709         totals = pool.getTotalStats();
710         Assert.assertEquals(0, totals.getAvailable());
711         Assert.assertEquals(0, totals.getLeased());
712         stats = pool.getStats("somehost");
713         Assert.assertEquals(0, stats.getAvailable());
714         Assert.assertEquals(0, stats.getLeased());
715     }
716 
717     @Test(expected=IllegalArgumentException.class)
718     public void testCloseIdleInvalid() throws Exception {
719         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
720         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
721         pool.closeIdle(50, null);
722     }
723 
724     @Test(expected=IllegalArgumentException.class)
725     public void testGetStatsInvalid() throws Exception {
726         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
727         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
728         pool.getStats(null);
729     }
730 
731     @Test
732     public void testSetMaxInvalid() throws Exception {
733         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
734         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
735         try {
736             pool.setMaxTotal(-1);
737             Assert.fail("IllegalArgumentException should have been thrown");
738         } catch (final IllegalArgumentException expected) {
739         }
740         try {
741             pool.setMaxPerRoute(null, 1);
742             Assert.fail("IllegalArgumentException should have been thrown");
743         } catch (final IllegalArgumentException expected) {
744         }
745         try {
746             pool.setDefaultMaxPerRoute(-1);
747             Assert.fail("IllegalArgumentException should have been thrown");
748         } catch (final IllegalArgumentException expected) {
749         }
750     }
751 
752     @Test
753     public void testSetMaxPerRoute() throws Exception {
754         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
755         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
756         pool.setMaxPerRoute("somehost", 1);
757         Assert.assertEquals(1, pool.getMaxPerRoute("somehost"));
758         pool.setMaxPerRoute("somehost", 0);
759         Assert.assertEquals(0, pool.getMaxPerRoute("somehost"));
760         pool.setMaxPerRoute("somehost", -1);
761         Assert.assertEquals(2, pool.getMaxPerRoute("somehost"));
762     }
763 
764     @Test
765     public void testShutdown() throws Exception {
766         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
767 
768         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
769         Mockito.when(conn1.isOpen()).thenReturn(true);
770         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
771         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
772         Mockito.when(conn2.isOpen()).thenReturn(true);
773         Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);
774 
775         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
776         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
777         final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
778         Assert.assertNotNull(entry1);
779         final Future<LocalPoolEntry> future2 = pool.lease("otherhost", null);
780         final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
781         Assert.assertNotNull(entry2);
782 
783         pool.release(entry2, true);
784 
785         final PoolStats totals = pool.getTotalStats();
786         Assert.assertEquals(1, totals.getAvailable());
787         Assert.assertEquals(1, totals.getLeased());
788 
789         pool.shutdown();
790         Assert.assertTrue(pool.isShutdown());
791         pool.shutdown();
792         pool.shutdown();
793 
794         Mockito.verify(conn1, Mockito.atLeastOnce()).close();
795         Mockito.verify(conn2, Mockito.atLeastOnce()).close();
796 
797         try {
798             pool.lease("somehost", null);
799             Assert.fail("IllegalStateException should have been thrown");
800         } catch (final IllegalStateException expected) {
801         }
802         // Ignored if shut down
803         pool.release(new LocalPoolEntry("somehost", Mockito.mock(HttpConnection.class)), true);
804     }
805 
806     @Test
807     public void testValidateConnectionNotStale() throws Exception {
808         final HttpConnection conn = Mockito.mock(HttpConnection.class);
809         Mockito.when(conn.isOpen()).thenReturn(true);
810         Mockito.when(conn.isStale()).thenReturn(false);
811 
812         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
813         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn);
814 
815         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
816         pool.setValidateAfterInactivity(100);
817 
818         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
819         final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
820         Assert.assertNotNull(entry1);
821 
822         pool.release(entry1, true);
823 
824         Thread.sleep(150);
825 
826         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
827         final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
828         Assert.assertNotNull(entry2);
829         Assert.assertSame(entry1, entry2);
830 
831         Mockito.verify(conn, Mockito.times(1)).isStale();
832     }
833 
834     @Test
835     public void testValidateConnectionStale() throws Exception {
836         final HttpConnection conn = Mockito.mock(HttpConnection.class);
837         Mockito.when(conn.isOpen()).thenReturn(true);
838         Mockito.when(conn.isStale()).thenReturn(false);
839 
840         final LocalConnFactory connFactory = Mockito.mock(LocalConnFactory.class);
841         Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn);
842 
843         final LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
844         pool.setValidateAfterInactivity(5);
845 
846         final Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
847         final LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
848         Assert.assertNotNull(entry1);
849 
850         pool.release(entry1, true);
851 
852         Thread.sleep(10);
853 
854         Mockito.verify(connFactory, Mockito.times(1)).create("somehost");
855         Mockito.when(conn.isStale()).thenReturn(true);
856 
857         final Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
858         final LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
859         Assert.assertNotNull(entry2);
860         Assert.assertNotSame(entry1, entry2);
861 
862         Mockito.verify(conn, Mockito.times(1)).isStale();
863         Mockito.verify(conn, Mockito.times(1)).close();
864         Mockito.verify(connFactory, Mockito.times(2)).create("somehost");
865     }
866 
867 }