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.Future;
31  import java.util.concurrent.TimeUnit;
32  
33  import org.apache.hc.core5.http.HttpConnection;
34  import org.apache.hc.core5.io.ShutdownType;
35  import org.apache.hc.core5.util.TimeValue;
36  import org.apache.hc.core5.util.Timeout;
37  import org.junit.Assert;
38  import org.junit.Test;
39  import org.mockito.Mockito;
40  
41  public class TestLaxConnPool {
42  
43      @Test
44      public void testEmptyPool() throws Exception {
45          final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
46          final PoolStats totals = pool.getTotalStats();
47          Assert.assertEquals(0, totals.getAvailable());
48          Assert.assertEquals(0, totals.getLeased());
49          Assert.assertEquals(0, totals.getPending());
50          Assert.assertEquals(0, totals.getMax());
51          Assert.assertEquals(Collections.emptySet(), pool.getRoutes());
52          final PoolStats stats = pool.getStats("somehost");
53          Assert.assertEquals(0, stats.getAvailable());
54          Assert.assertEquals(0, stats.getLeased());
55          Assert.assertEquals(0, stats.getPending());
56          Assert.assertEquals(2, stats.getMax());
57          Assert.assertEquals("[leased: 0][available: 0][pending: 0]", pool.toString());
58      }
59  
60      @Test
61      public void testInvalidConstruction() throws Exception {
62          try {
63              new LaxConnPool<String, HttpConnection>(-1);
64              Assert.fail("IllegalArgumentException should have been thrown");
65          } catch (final IllegalArgumentException expected) {
66          }
67      }
68  
69      @Test
70      public void testLeaseRelease() throws Exception {
71          final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
72          final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
73          final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
74  
75          final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
76          final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
77          final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
78          final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
79  
80          final PoolEntry<String, HttpConnection> entry1 = future1.get();
81          Assert.assertNotNull(entry1);
82          entry1.assignConnection(conn1);
83          final PoolEntry<String, HttpConnection> entry2 = future2.get();
84          Assert.assertNotNull(entry2);
85          entry2.assignConnection(conn2);
86          final PoolEntry<String, HttpConnection> entry3 = future3.get();
87          Assert.assertNotNull(entry3);
88          entry3.assignConnection(conn3);
89  
90          pool.release(entry1, true);
91          pool.release(entry2, true);
92          pool.release(entry3, false);
93          Mockito.verify(conn1, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
94          Mockito.verify(conn2, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
95          Mockito.verify(conn3, Mockito.times(1)).shutdown(ShutdownType.GRACEFUL);
96  
97          final PoolStats totals = pool.getTotalStats();
98          Assert.assertEquals(2, totals.getAvailable());
99          Assert.assertEquals(0, totals.getLeased());
100         Assert.assertEquals(0, totals.getPending());
101     }
102 
103     @Test
104     public void testLeaseIllegal() throws Exception {
105         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
106         try {
107             pool.lease(null, null, Timeout.ZERO_MILLISECONDS, null);
108             Assert.fail("IllegalArgumentException should have been thrown");
109         } catch (final IllegalArgumentException expected) {
110         }
111     }
112 
113     @Test(expected = IllegalStateException.class)
114     public void testReleaseUnknownEntry() throws Exception {
115         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
116         pool.release(new PoolEntry<String, HttpConnection>("somehost"), true);
117     }
118 
119     @Test
120     public void testMaxLimits() throws Exception {
121         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
122         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
123         final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
124 
125         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
126         pool.setMaxPerRoute("somehost", 2);
127         pool.setMaxPerRoute("otherhost", 1);
128 
129         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
130         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
131         final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
132 
133         final PoolEntry<String, HttpConnection> entry1 = future1.get();
134         Assert.assertNotNull(entry1);
135         entry1.assignConnection(conn1);
136         final PoolEntry<String, HttpConnection> entry2 = future2.get();
137         Assert.assertNotNull(entry2);
138         entry2.assignConnection(conn2);
139         final PoolEntry<String, HttpConnection> entry3 = future3.get();
140         Assert.assertNotNull(entry3);
141         entry3.assignConnection(conn3);
142 
143         pool.release(entry1, true);
144         pool.release(entry2, true);
145         pool.release(entry3, true);
146 
147         final PoolStats totals = pool.getTotalStats();
148         Assert.assertEquals(3, totals.getAvailable());
149         Assert.assertEquals(0, totals.getLeased());
150         Assert.assertEquals(0, totals.getPending());
151 
152         final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("somehost", null);
153         final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", null);
154         final Future<PoolEntry<String, HttpConnection>> future6 = pool.lease("otherhost", null);
155         final Future<PoolEntry<String, HttpConnection>> future7 = pool.lease("somehost", null);
156         final Future<PoolEntry<String, HttpConnection>> future8 = pool.lease("somehost", null);
157         final Future<PoolEntry<String, HttpConnection>> future9 = pool.lease("otherhost", null);
158 
159         Assert.assertTrue(future4.isDone());
160         final PoolEntry<String, HttpConnection> entry4 = future4.get();
161         Assert.assertNotNull(entry4);
162         Assert.assertSame(conn2, entry4.getConnection());
163 
164         Assert.assertTrue(future5.isDone());
165         final PoolEntry<String, HttpConnection> entry5 = future5.get();
166         Assert.assertNotNull(entry5);
167         Assert.assertSame(conn1, entry5.getConnection());
168 
169         Assert.assertTrue(future6.isDone());
170         final PoolEntry<String, HttpConnection> entry6 = future6.get();
171         Assert.assertNotNull(entry6);
172         Assert.assertSame(conn3, entry6.getConnection());
173 
174         Assert.assertFalse(future7.isDone());
175         Assert.assertFalse(future8.isDone());
176         Assert.assertFalse(future9.isDone());
177 
178         pool.release(entry4, true);
179         pool.release(entry5, false);
180         pool.release(entry6, true);
181 
182         Assert.assertTrue(future7.isDone());
183         final PoolEntry<String, HttpConnection> entry7 = future7.get();
184         Assert.assertNotNull(entry7);
185         Assert.assertSame(conn2, entry7.getConnection());
186 
187         Assert.assertTrue(future8.isDone());
188         final PoolEntry<String, HttpConnection> entry8 = future8.get();
189         Assert.assertNotNull(entry8);
190         Assert.assertEquals(null, entry8.getConnection());
191 
192         Assert.assertTrue(future9.isDone());
193         final PoolEntry<String, HttpConnection> entry9 = future9.get();
194         Assert.assertNotNull(entry9);
195         Assert.assertSame(conn3, entry9.getConnection());
196     }
197 
198     @Test
199     public void testCreateNewIfExpired() throws Exception {
200         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
201 
202         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
203 
204         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
205 
206         Assert.assertTrue(future1.isDone());
207         final PoolEntry<String, HttpConnection> entry1 = future1.get();
208         Assert.assertNotNull(entry1);
209         entry1.assignConnection(conn1);
210 
211         entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
212         pool.release(entry1, true);
213 
214         Thread.sleep(200L);
215 
216         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
217 
218         Assert.assertTrue(future2.isDone());
219 
220         Mockito.verify(conn1).shutdown(ShutdownType.GRACEFUL);
221 
222         final PoolStats totals = pool.getTotalStats();
223         Assert.assertEquals(0, totals.getAvailable());
224         Assert.assertEquals(1, totals.getLeased());
225         Assert.assertEquals(Collections.singleton("somehost"), pool.getRoutes());
226         final PoolStats stats = pool.getStats("somehost");
227         Assert.assertEquals(0, stats.getAvailable());
228         Assert.assertEquals(1, stats.getLeased());
229     }
230 
231     @Test
232     public void testCloseExpired() throws Exception {
233         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
234         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
235 
236         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
237 
238         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
239         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
240 
241         Assert.assertTrue(future1.isDone());
242         final PoolEntry<String, HttpConnection> entry1 = future1.get();
243         Assert.assertNotNull(entry1);
244         entry1.assignConnection(conn1);
245         Assert.assertTrue(future2.isDone());
246         final PoolEntry<String, HttpConnection> entry2 = future2.get();
247         Assert.assertNotNull(entry2);
248         entry2.assignConnection(conn2);
249 
250         entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
251         pool.release(entry1, true);
252 
253         Thread.sleep(200);
254 
255         entry2.updateExpiry(TimeValue.of(1000, TimeUnit.SECONDS));
256         pool.release(entry2, true);
257 
258         pool.closeExpired();
259 
260         Mockito.verify(conn1).shutdown(ShutdownType.GRACEFUL);
261         Mockito.verify(conn2, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
262 
263         final PoolStats totals = pool.getTotalStats();
264         Assert.assertEquals(1, totals.getAvailable());
265         Assert.assertEquals(0, totals.getLeased());
266         Assert.assertEquals(0, totals.getPending());
267         final PoolStats stats = pool.getStats("somehost");
268         Assert.assertEquals(1, stats.getAvailable());
269         Assert.assertEquals(0, stats.getLeased());
270         Assert.assertEquals(0, stats.getPending());
271     }
272 
273     @Test
274     public void testCloseIdle() throws Exception {
275         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
276         final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
277 
278         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
279 
280         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
281         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
282 
283         Assert.assertTrue(future1.isDone());
284         final PoolEntry<String, HttpConnection> entry1 = future1.get();
285         Assert.assertNotNull(entry1);
286         entry1.assignConnection(conn1);
287         Assert.assertTrue(future2.isDone());
288         final PoolEntry<String, HttpConnection> entry2 = future2.get();
289         Assert.assertNotNull(entry2);
290         entry2.assignConnection(conn2);
291 
292         entry1.updateState(null);
293         pool.release(entry1, true);
294 
295         Thread.sleep(200L);
296 
297         entry2.updateState(null);
298         pool.release(entry2, true);
299 
300         pool.closeIdle(TimeValue.of(50, TimeUnit.MILLISECONDS));
301 
302         Mockito.verify(conn1).shutdown(ShutdownType.GRACEFUL);
303         Mockito.verify(conn2, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
304 
305         PoolStats totals = pool.getTotalStats();
306         Assert.assertEquals(1, totals.getAvailable());
307         Assert.assertEquals(0, totals.getLeased());
308         Assert.assertEquals(0, totals.getPending());
309         PoolStats stats = pool.getStats("somehost");
310         Assert.assertEquals(1, stats.getAvailable());
311         Assert.assertEquals(0, stats.getLeased());
312         Assert.assertEquals(0, stats.getPending());
313 
314         pool.closeIdle(TimeValue.of(-1, TimeUnit.MILLISECONDS));
315 
316         Mockito.verify(conn2).shutdown(ShutdownType.GRACEFUL);
317 
318         totals = pool.getTotalStats();
319         Assert.assertEquals(0, totals.getAvailable());
320         Assert.assertEquals(0, totals.getLeased());
321         Assert.assertEquals(0, totals.getPending());
322         stats = pool.getStats("somehost");
323         Assert.assertEquals(0, stats.getAvailable());
324         Assert.assertEquals(0, stats.getLeased());
325         Assert.assertEquals(0, stats.getPending());
326     }
327 
328     @Test
329     public void testLeaseRequestTimeout() throws Exception {
330         final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
331 
332         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(1);
333 
334         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
335         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
336         final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("somehost", null, Timeout.ofMillis(10), null);
337 
338         Assert.assertTrue(future1.isDone());
339         final PoolEntry<String, HttpConnection> entry1 = future1.get();
340         Assert.assertNotNull(entry1);
341         entry1.assignConnection(conn1);
342         Assert.assertFalse(future2.isDone());
343         Assert.assertFalse(future3.isDone());
344 
345         Thread.sleep(100);
346 
347         pool.validatePendingRequests();
348 
349         Assert.assertFalse(future2.isDone());
350         Assert.assertTrue(future3.isDone());
351     }
352 
353     @Test
354     public void testLeaseRequestCanceled() throws Exception {
355         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(1);
356 
357         final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
358 
359         Assert.assertTrue(future1.isDone());
360         final PoolEntry<String, HttpConnection> entry1 = future1.get();
361         Assert.assertNotNull(entry1);
362         entry1.assignConnection(Mockito.mock(HttpConnection.class));
363 
364         final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
365         future2.cancel(true);
366 
367         pool.release(entry1, true);
368 
369         final PoolStats totals = pool.getTotalStats();
370         Assert.assertEquals(1, totals.getAvailable());
371         Assert.assertEquals(0, totals.getLeased());
372     }
373 
374     @Test(expected=IllegalArgumentException.class)
375     public void testGetStatsInvalid() throws Exception {
376         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
377         pool.getStats(null);
378     }
379 
380     @Test
381     public void testSetMaxInvalid() throws Exception {
382         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
383         try {
384             pool.setMaxPerRoute(null, 1);
385             Assert.fail("IllegalArgumentException should have been thrown");
386         } catch (final IllegalArgumentException expected) {
387         }
388         try {
389             pool.setMaxPerRoute("somehost", -1);
390             Assert.fail("IllegalArgumentException should have been thrown");
391         } catch (final IllegalArgumentException expected) {
392         }
393         try {
394             pool.setDefaultMaxPerRoute(-1);
395             Assert.fail("IllegalArgumentException should have been thrown");
396         } catch (final IllegalArgumentException expected) {
397         }
398     }
399 
400     @Test
401     public void testShutdown() throws Exception {
402         final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2);
403         pool.shutdown(ShutdownType.GRACEFUL);
404         try {
405             pool.lease("somehost", null);
406             Assert.fail("IllegalStateException should have been thrown");
407         } catch (final IllegalStateException expected) {
408         }
409         // Ignored if shut down
410         pool.release(new PoolEntry<String, HttpConnection>("somehost"), true);
411     }
412 
413 }