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.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
406 pool.release(new PoolEntry<String, HttpConnection>("somehost"), true);
407 }
408
409 }