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