View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Tests for {@link AbandonedConfig}.
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                  // expected in most cases
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                  // ignore
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             // while destroying instances, yield control to other threads
111             // helps simulate threading errors
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         // Uncomment the following line to enable logging:
146         // abandonedConfig.setLogAbandoned(true);
147 
148         // One second Duration.
149         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
150         assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
151         assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout());
152         // One second int (not millis).
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         // There should be no registered pools at this point
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             // Clean these up ready for the next test
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      * Verify that an object that gets flagged as abandoned and is subsequently
192      * invalidated is only destroyed (and pool counter decremented) once.
193      *
194      * @throws InterruptedException May occur in some failure modes
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();  // Unregister pool created by setup
202         pool = new GenericKeyedObjectPool<>(
203                 // destroys take 100 millis
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); // abandon checked out instances and let evictor start
216         if (!pool.getKeys().contains(key)) {
217             Thread.sleep(1000); // Wait a little more.
218         }
219         if (!pool.getKeys().contains(key)) {
220             Thread.sleep(1000); // Wait a little more.
221         }
222         pool.invalidateObject(key, pooledObj); // Should not trigger another destroy / decrement
223         Thread.sleep(2000); // give evictor time to finish destroys
224         assertEquals(0, pool.getNumActive());
225         assertEquals(5, pool.getDestroyedCount());
226     }
227 
228     /**
229      * Verify that an object that gets flagged as abandoned and is subsequently returned
230      * is destroyed instead of being returned to the pool (and possibly later destroyed
231      * inappropriately).
232      *
233      * @throws Exception May occur in some failure modes
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();  // Unregister pool created by setup
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);  // abandon checked out instances
255         // Now start a race - returner waits until borrowObject has kicked
256         // off removeAbandoned and then returns an instance that borrowObject
257         // will deem abandoned.  Make sure it is not returned to the borrower.
258         returner.start();    // short delay, then return instance
259         assertTrue(pool.borrowObject(0).hashCode() != deadMansHash);
260         assertEquals(0, pool.getNumIdle());
261         assertEquals(1, pool.getNumActive());
262     }
263 
264     /**
265      * Tests fix for Bug 28579, a bug in AbandonedObjectPool that causes numActive to go negative
266      * in GenericKeyedObjectPool
267      *
268      * @throws Exception May occur in some failure modes
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         // Exhaust the connection pool
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         // Abandon all borrowed objects
284         for (final PooledTestObject element : vec) {
285             element.setAbandoned(true);
286         }
287 
288         // Try launching a bunch of borrows concurrently.  Abandoned sweep will be triggered for each.
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         // Wait for all the threads to finish
297         for (int i = 0; i < CONCURRENT_BORROWS; i++) {
298             threads[i].join();
299         }
300 
301         // Return all objects that have not been destroyed
302         for (final PooledTestObject pto : vec) {
303             if (pto.isActive()) {
304                 pool.returnObject(0, pto);
305             }
306         }
307 
308         // Now, the number of active instances should be 0
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();  // Unregister pool created by setup
317         pool = new GenericKeyedObjectPool<>(
318              // validate takes 1 second
319              new SimpleFactory(0, 0),
320              new GenericKeyedObjectPoolConfig<>(), abandonedConfig);
321         pool.setTimeBetweenEvictionRuns(Duration.ofMillis(50));
322         // Borrow an object, wait long enough for it to be abandoned
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();  // Unregister pool created by setup
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      * Verify that an object that the evictor identifies as abandoned while it
341      * is in process of being returned to the pool is not destroyed.
342      *
343      * @throws Exception May occur in some failure modes
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();  // Unregister pool created by setup
351         pool = new GenericKeyedObjectPool<>(
352              // validate takes 1 second
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         // Borrow an object, wait long enough for it to be abandoned
361         // then arrange for evictor to run while it is being returned
362         // validation takes a second, evictor runs every 500 ms
363         final PooledTestObject obj = pool.borrowObject(0);
364         Thread.sleep(50);       // abandon obj
365         pool.returnObject(0, obj); // evictor will run during validation
366         final PooledTestObject obj2 = pool.borrowObject(0);
367         assertEquals(obj, obj2);          // should get original back
368         assertFalse(obj2.isDestroyed());  // and not destroyed
369     }
370 
371     /**
372      * JIRA: POOL-300
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      * Test case for https://issues.apache.org/jira/browse/DBCP-260.
394      * Borrow and abandon all the available objects then attempt to borrow one
395      * further object which should block until the abandoned objects are
396      * removed. We don't want the test to block indefinitely when it fails so
397      * use maxWait be check we don't actually have to wait that long.
398      *
399      * @throws Exception May occur in some failure modes
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") // This is going to be abandoned
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