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