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