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 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
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
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 }