1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
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
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 }