1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.pool2.impl;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23
24 import java.io.BufferedOutputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.PrintWriter;
27 import java.lang.management.ManagementFactory;
28 import java.time.Duration;
29 import java.time.Instant;
30 import java.util.ArrayList;
31 import java.util.Objects;
32 import java.util.Set;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import javax.management.MBeanServer;
36 import javax.management.ObjectName;
37
38 import org.apache.commons.pool2.DestroyMode;
39 import org.apache.commons.pool2.PooledObject;
40 import org.apache.commons.pool2.PooledObjectFactory;
41 import org.apache.commons.pool2.TrackedUse;
42 import org.apache.commons.pool2.Waiter;
43 import org.junit.jupiter.api.AfterEach;
44 import org.junit.jupiter.api.BeforeEach;
45 import org.junit.jupiter.api.Test;
46
47 class PooledTestObject implements TrackedUse {
48 private static final AtomicInteger ATOMIC_HASH = new AtomicInteger();
49 private static final Instant INSTANT_0 = Instant.ofEpochMilli(0);
50 private static final Instant INSTANT_1 = Instant.ofEpochMilli(1);
51 private boolean active;
52 private boolean destroyed;
53 private final int hash;
54 private boolean abandoned;
55 private boolean detached;
56
57 public PooledTestObject() {
58 this.hash = ATOMIC_HASH.incrementAndGet();
59 }
60
61 public void destroy(final DestroyMode mode) {
62 destroyed = true;
63 if (mode.equals(DestroyMode.ABANDONED)) {
64 detached = true;
65 }
66 }
67
68 @Override
69 public boolean equals(final Object obj) {
70 if (!(obj instanceof PooledTestObject)) {
71 return false;
72 }
73 return obj.hashCode() == hashCode();
74 }
75
76 @Override
77 public long getLastUsed() {
78 if (abandoned) {
79
80
81 return 1;
82 }
83
84 return 0;
85 }
86
87 @Override
88 public Instant getLastUsedInstant() {
89 if (abandoned) {
90
91
92 return INSTANT_1;
93 }
94
95 return INSTANT_0;
96 }
97
98 @Override
99 public int hashCode() {
100 return hash;
101 }
102
103 public synchronized boolean isActive() {
104 return active;
105 }
106
107 public boolean isDestroyed() {
108 return destroyed;
109 }
110
111 public boolean isDetached() {
112 return detached;
113 }
114
115 public void setAbandoned(final boolean b) {
116 abandoned = b;
117 }
118
119 public synchronized void setActive(final boolean b) {
120 active = b;
121 }
122 }
123
124
125
126
127 public class TestAbandonedObjectPool {
128
129 class ConcurrentBorrower extends Thread {
130 private final ArrayList<PooledTestObject> borrowed;
131
132 public ConcurrentBorrower(final ArrayList<PooledTestObject> borrowed) {
133 this.borrowed = borrowed;
134 }
135
136 @Override
137 public void run() {
138 try {
139 borrowed.add(pool.borrowObject());
140 } catch (final Exception e) {
141
142 }
143 }
144 }
145 class ConcurrentReturner extends Thread {
146 private final PooledTestObject returned;
147 public ConcurrentReturner(final PooledTestObject obj) {
148 returned = obj;
149 }
150 @Override
151 public void run() {
152 try {
153 sleep(20);
154 pool.returnObject(returned);
155 } catch (final Exception e) {
156
157 }
158 }
159 }
160
161 private static class SimpleFactory implements PooledObjectFactory<PooledTestObject> {
162
163 private final long destroyLatency;
164 private final long validateLatency;
165
166 public SimpleFactory() {
167 destroyLatency = 0;
168 validateLatency = 0;
169 }
170
171 public SimpleFactory(final long destroyLatency, final long validateLatency) {
172 this.destroyLatency = destroyLatency;
173 this.validateLatency = validateLatency;
174 }
175
176 @Override
177 public void activateObject(final PooledObject<PooledTestObject> obj) {
178 obj.getObject().setActive(true);
179 }
180
181 @Override
182 public void destroyObject(final PooledObject<PooledTestObject> obj) throws InterruptedException {
183 destroyObject(obj, DestroyMode.NORMAL);
184 }
185
186 @Override
187 public void destroyObject(final PooledObject<PooledTestObject> obj, final DestroyMode destroyMode) throws InterruptedException {
188 obj.getObject().setActive(false);
189
190
191 Thread.yield();
192 if (destroyLatency != 0) {
193 Thread.sleep(destroyLatency);
194 }
195 obj.getObject().destroy(destroyMode);
196 }
197
198 @Override
199 public PooledObject<PooledTestObject> makeObject() {
200 return new DefaultPooledObject<>(new PooledTestObject());
201 }
202
203 @Override
204 public void passivateObject(final PooledObject<PooledTestObject> obj) {
205 obj.getObject().setActive(false);
206 }
207
208 @Override
209 public boolean validateObject(final PooledObject<PooledTestObject> obj) {
210 Waiter.sleepQuietly(validateLatency);
211 return true;
212 }
213 }
214
215 private GenericObjectPool<PooledTestObject> pool;
216
217 private AbandonedConfig abandonedConfig;
218
219 @SuppressWarnings("deprecation")
220 @BeforeEach
221 public void setUp() {
222 abandonedConfig = new AbandonedConfig();
223
224
225
226
227 abandonedConfig.setRemoveAbandonedOnBorrow(true);
228
229 abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
230 assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
231 assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout());
232
233 abandonedConfig.setRemoveAbandonedTimeout(1);
234 assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
235 assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout());
236
237 pool = new GenericObjectPool<>(
238 new SimpleFactory(),
239 new GenericObjectPoolConfig<>(),
240 abandonedConfig);
241 }
242
243 @AfterEach
244 public void tearDown() throws Exception {
245 final ObjectName jmxName = pool.getJmxName();
246 final String poolName = Objects.toString(jmxName, null);
247 pool.clear();
248 pool.close();
249 pool = null;
250
251 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
252 final Set<ObjectName> result = mbs.queryNames(new ObjectName(
253 "org.apache.commoms.pool2:type=GenericObjectPool,*"), null);
254
255 final int registeredPoolCount = result.size();
256 final StringBuilder msg = new StringBuilder("Current pool is: ");
257 msg.append(poolName);
258 msg.append(" Still open pools are: ");
259 for (final ObjectName name : result) {
260
261 msg.append(name.toString());
262 msg.append(" created via\n");
263 msg.append(mbs.getAttribute(name, "CreationStackTrace"));
264 msg.append('\n');
265 mbs.unregisterMBean(name);
266 }
267 assertEquals(0, registeredPoolCount, msg.toString());
268 }
269
270
271
272
273
274
275
276 @Test
277 public void testAbandonedInvalidate() throws Exception {
278 abandonedConfig = new AbandonedConfig();
279 abandonedConfig.setRemoveAbandonedOnMaintenance(true);
280 abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
281 pool.close();
282 pool = new GenericObjectPool<>(
283
284 new SimpleFactory(200, 0),
285 new GenericObjectPoolConfig<>(), abandonedConfig);
286 final int n = 10;
287 pool.setMaxTotal(n);
288 pool.setBlockWhenExhausted(false);
289 pool.setDurationBetweenEvictionRuns(Duration.ofMillis(500));
290 PooledTestObject obj = null;
291 for (int i = 0; i < 5; i++) {
292 obj = pool.borrowObject();
293 }
294 Thread.sleep(1000);
295 pool.invalidateObject(obj);
296 Thread.sleep(2000);
297 assertEquals(0, pool.getNumActive());
298 assertEquals(5, pool.getDestroyedCount());
299 }
300
301
302
303
304
305
306
307
308 @Test
309 public void testAbandonedReturn() throws Exception {
310 abandonedConfig = new AbandonedConfig();
311 abandonedConfig.setRemoveAbandonedOnBorrow(true);
312 abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
313 pool.close();
314 pool = new GenericObjectPool<>(
315 new SimpleFactory(200, 0),
316 new GenericObjectPoolConfig<>(), abandonedConfig);
317 final int n = 10;
318 pool.setMaxTotal(n);
319 pool.setBlockWhenExhausted(false);
320 PooledTestObject obj = null;
321 for (int i = 0; i < n - 2; i++) {
322 obj = pool.borrowObject();
323 }
324 Objects.requireNonNull(obj, "Unable to borrow object from pool");
325 final int deadMansHash = obj.hashCode();
326 final ConcurrentReturner returner = new ConcurrentReturner(obj);
327 Thread.sleep(2000);
328
329
330
331 returner.start();
332 assertTrue(pool.borrowObject().hashCode() != deadMansHash);
333 assertEquals(0, pool.getNumIdle());
334 assertEquals(1, pool.getNumActive());
335 }
336
337
338
339
340
341
342
343 @Test
344 public void testConcurrentInvalidation() throws Exception {
345 final int POOL_SIZE = 30;
346 pool.setMaxTotal(POOL_SIZE);
347 pool.setMaxIdle(POOL_SIZE);
348 pool.setBlockWhenExhausted(false);
349
350
351 final ArrayList<PooledTestObject> vec = new ArrayList<>();
352 for (int i = 0; i < POOL_SIZE; i++) {
353 vec.add(pool.borrowObject());
354 }
355
356
357 for (final PooledTestObject element : vec) {
358 element.setAbandoned(true);
359 }
360
361
362 final int CONCURRENT_BORROWS = 5;
363 final Thread[] threads = new Thread[CONCURRENT_BORROWS];
364 for (int i = 0; i < CONCURRENT_BORROWS; i++) {
365 threads[i] = new ConcurrentBorrower(vec);
366 threads[i].start();
367 }
368
369
370 for (int i = 0; i < CONCURRENT_BORROWS; i++) {
371 threads[i].join();
372 }
373
374
375 for (final PooledTestObject pto : vec) {
376 if (pto.isActive()) {
377 pool.returnObject(pto);
378 }
379 }
380
381
382 assertEquals(0, pool.getNumActive(), "numActive should have been 0, was " + pool.getNumActive());
383 }
384
385 public void testDestroyModeAbandoned() throws Exception {
386 abandonedConfig = new AbandonedConfig();
387 abandonedConfig.setRemoveAbandonedOnMaintenance(true);
388 abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
389 pool.close();
390 pool = new GenericObjectPool<>(
391
392 new SimpleFactory(0, 0),
393 new GenericObjectPoolConfig<>(), abandonedConfig);
394 pool.setTimeBetweenEvictionRuns(Duration.ofMillis(50));
395
396 final PooledTestObject obj = pool.borrowObject();
397 Thread.sleep(100);
398 assertTrue(obj.isDetached());
399 }
400
401 public void testDestroyModeNormal() throws Exception {
402 abandonedConfig = new AbandonedConfig();
403 pool.close();
404 pool = new GenericObjectPool<>(new SimpleFactory(0, 0));
405 pool.setMaxIdle(0);
406 final PooledTestObject obj = pool.borrowObject();
407 pool.returnObject(obj);
408 assertTrue(obj.isDestroyed());
409 assertFalse(obj.isDetached());
410 }
411
412
413
414
415
416
417
418 @Test
419 public void testRemoveAbandonedWhileReturning() throws Exception {
420 abandonedConfig = new AbandonedConfig();
421 abandonedConfig.setRemoveAbandonedOnMaintenance(true);
422 abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
423 pool.close();
424 pool = new GenericObjectPool<>(
425
426 new SimpleFactory(0, 1000),
427 new GenericObjectPoolConfig<>(), abandonedConfig);
428 final int n = 10;
429 pool.setMaxTotal(n);
430 pool.setBlockWhenExhausted(false);
431 pool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
432 pool.setTestOnReturn(true);
433
434
435
436 final PooledTestObject obj = pool.borrowObject();
437 Thread.sleep(50);
438 pool.returnObject(obj);
439 final PooledTestObject obj2 = pool.borrowObject();
440 assertEquals(obj, obj2);
441 assertFalse(obj2.isDestroyed());
442 }
443
444
445
446
447 @Test
448 public void testStackTrace() throws Exception {
449 abandonedConfig.setRemoveAbandonedOnMaintenance(true);
450 abandonedConfig.setLogAbandoned(true);
451 abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
452 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
453 final BufferedOutputStream bos = new BufferedOutputStream(baos);
454 final PrintWriter pw = new PrintWriter(bos);
455 abandonedConfig.setLogWriter(pw);
456 pool.setAbandonedConfig(abandonedConfig);
457 pool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
458 final PooledTestObject o1 = pool.borrowObject();
459 Thread.sleep(2000);
460 assertTrue(o1.isDestroyed());
461 bos.flush();
462 assertTrue(baos.toString().indexOf("Pooled object") >= 0);
463 }
464
465
466
467
468
469
470
471
472
473
474 @Test
475 public void testWhenExhaustedBlock() throws Exception {
476 abandonedConfig.setRemoveAbandonedOnMaintenance(true);
477 pool.setAbandonedConfig(abandonedConfig);
478 pool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
479
480 pool.setMaxTotal(1);
481
482 @SuppressWarnings("unused")
483 final PooledTestObject o1 = pool.borrowObject();
484
485 final long startMillis = System.currentTimeMillis();
486 final PooledTestObject o2 = pool.borrowObject(5000);
487 final long endMillis = System.currentTimeMillis();
488
489 pool.returnObject(o2);
490
491 assertTrue(endMillis - startMillis < 5000);
492 }
493 }
494