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