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