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    *      http://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  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                  // expected in most cases
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                  // ignore
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             // while destroying instances, yield control to other threads
110             // helps simulate threading errors
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         // Uncomment the following line to enable logging:
145         // abandonedConfig.setLogAbandoned(true);
146 
147         // One second Duration.
148         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
149         assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
150         assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout());
151         // One second int (not millis).
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         // There should be no registered pools at this point
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             // Clean these up ready for the next test
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      * Verify that an object that gets flagged as abandoned and is subsequently
191      * invalidated is only destroyed (and pool counter decremented) once.
192      *
193      * @throws InterruptedException May occur in some failure modes
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();  // Unregister pool created by setup
201         pool = new GenericKeyedObjectPool<>(
202                 // destroys take 100 millis
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); // abandon checked out instances and let evictor start
215         if (!pool.getKeys().contains(key)) {
216             Thread.sleep(1000); // Wait a little more.
217         }
218         if (!pool.getKeys().contains(key)) {
219             Thread.sleep(1000); // Wait a little more.
220         }
221         pool.invalidateObject(key, pooledObj); // Should not trigger another destroy / decrement
222         Thread.sleep(2000); // give evictor time to finish destroys
223         assertEquals(0, pool.getNumActive());
224         assertEquals(5, pool.getDestroyedCount());
225     }
226 
227     /**
228      * Verify that an object that gets flagged as abandoned and is subsequently returned
229      * is destroyed instead of being returned to the pool (and possibly later destroyed
230      * inappropriately).
231      *
232      * @throws Exception May occur in some failure modes
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();  // Unregister pool created by setup
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);  // abandon checked out instances
254         // Now start a race - returner waits until borrowObject has kicked
255         // off removeAbandoned and then returns an instance that borrowObject
256         // will deem abandoned.  Make sure it is not returned to the borrower.
257         returner.start();    // short delay, then return instance
258         assertTrue(pool.borrowObject(0).hashCode() != deadMansHash);
259         assertEquals(0, pool.getNumIdle());
260         assertEquals(1, pool.getNumActive());
261     }
262 
263     /**
264      * Tests fix for Bug 28579, a bug in AbandonedObjectPool that causes numActive to go negative
265      * in GenericKeyedObjectPool
266      *
267      * @throws Exception May occur in some failure modes
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         // Exhaust the connection pool
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         // Abandon all borrowed objects
283         for (final PooledTestObject element : vec) {
284             element.setAbandoned(true);
285         }
286 
287         // Try launching a bunch of borrows concurrently.  Abandoned sweep will be triggered for each.
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         // Wait for all the threads to finish
296         for (int i = 0; i < CONCURRENT_BORROWS; i++) {
297             threads[i].join();
298         }
299 
300         // Return all objects that have not been destroyed
301         for (final PooledTestObject pto : vec) {
302             if (pto.isActive()) {
303                 pool.returnObject(0, pto);
304             }
305         }
306 
307         // Now, the number of active instances should be 0
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();  // Unregister pool created by setup
316         pool = new GenericKeyedObjectPool<>(
317              // validate takes 1 second
318              new SimpleFactory(0, 0),
319              new GenericKeyedObjectPoolConfig<>(), abandonedConfig);
320         pool.setTimeBetweenEvictionRuns(Duration.ofMillis(50));
321         // Borrow an object, wait long enough for it to be abandoned
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();  // Unregister pool created by setup
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      * Verify that an object that the evictor identifies as abandoned while it
340      * is in process of being returned to the pool is not destroyed.
341      *
342      * @throws Exception May occur in some failure modes
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();  // Unregister pool created by setup
350         pool = new GenericKeyedObjectPool<>(
351              // validate takes 1 second
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         // Borrow an object, wait long enough for it to be abandoned
360         // then arrange for evictor to run while it is being returned
361         // validation takes a second, evictor runs every 500 ms
362         final PooledTestObject obj = pool.borrowObject(0);
363         Thread.sleep(50);       // abandon obj
364         pool.returnObject(0, obj); // evictor will run during validation
365         final PooledTestObject obj2 = pool.borrowObject(0);
366         assertEquals(obj, obj2);          // should get original back
367         assertFalse(obj2.isDestroyed());  // and not destroyed
368     }
369 
370     /**
371      * JIRA: POOL-300
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      * Test case for https://issues.apache.org/jira/browse/DBCP-260.
393      * Borrow and abandon all the available objects then attempt to borrow one
394      * further object which should block until the abandoned objects are
395      * removed. We don't want the test to block indefinitely when it fails so
396      * use maxWait be check we don't actually have to wait that long.
397      *
398      * @throws Exception May occur in some failure modes
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") // This is going to be abandoned
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