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