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