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