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.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;  // destroy-abandoned "detaches"
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              // Abandoned object sweep will occur no matter what the value of removeAbandonedTimeout,
80              // because this indicates that this object was last used decades ago
81              return 1;
82          }
83          // Abandoned object sweep won't clean up this object
84          return 0;
85      }
86  
87      @Override
88      public Instant getLastUsedInstant() {
89          if (abandoned) {
90              // Abandoned object sweep will occur no matter what the value of removeAbandonedTimeout,
91              // because this indicates that this object was last used decades ago
92              return INSTANT_1;
93          }
94          // Abandoned object sweep won't clean up this object
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  * TestCase for AbandonedObjectPool
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                 // expected in most cases
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                 // ignore
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             // while destroying instances, yield control to other threads
193             // helps simulate threading errors
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         // Uncomment the following line to enable logging:
228         // abandonedConfig.setLogAbandoned(true);
229 
230         abandonedConfig.setRemoveAbandonedOnBorrow(true);
231         // One second Duration.
232         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
233         assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
234         assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout()); // in seconds.
235         // One second int (not millis).
236         abandonedConfig.setRemoveAbandonedTimeout(1);
237         assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
238         assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout()); // in seconds.
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         // There should be no registered pools at this point
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             // Clean these up ready for the next test
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      * Verify that an object that gets flagged as abandoned and is subsequently
275      * invalidated is only destroyed (and pool counter decremented) once.
276      *
277      * @throws Exception May occur in some failure modes
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();  // Unregister pool created by setup
285         pool = new GenericObjectPool<>(
286                 // destroys take 200 ms
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);          // abandon checked out instances and let evictor start
298         pool.invalidateObject(obj);  // Should not trigger another destroy / decrement
299         Thread.sleep(2000);          // give evictor time to finish destroys
300         assertEquals(0, pool.getNumActive());
301         assertEquals(5, pool.getDestroyedCount());
302     }
303 
304     /**
305      * Verify that an object that gets flagged as abandoned and is subsequently returned
306      * is destroyed instead of being returned to the pool (and possibly later destroyed
307      * inappropriately).
308      *
309      * @throws Exception May occur in some failure modes
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();  // Unregister pool created by setup
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);  // abandon checked out instances
331         // Now start a race - returner waits until borrowObject has kicked
332         // off removeAbandoned and then returns an instance that borrowObject
333         // will deem abandoned.  Make sure it is not returned to the borrower.
334         returner.start();    // short delay, then return instance
335         assertTrue(pool.borrowObject().hashCode() != deadMansHash);
336         // There should be n - 2 - 1 idle instance in the pool
337         // The n - 2 instances deemed abandoned should have been replaced, but
338         // one of the new ones has been checked out.
339         assertEquals(n - 2 - 1, pool.getNumIdle());
340         assertEquals(1, pool.getNumActive());
341     }
342 
343     /**
344      * Tests fix for Bug 28579, a bug in AbandonedObjectPool that causes numActive to go negative
345      * in GenericObjectPool
346      *
347      * @throws Exception May occur in some failure modes
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         // Exhaust the connection pool
357         final ArrayList<PooledTestObject> vec = new ArrayList<>();
358         for (int i = 0; i < POOL_SIZE; i++) {
359             vec.add(pool.borrowObject());
360         }
361 
362         // Abandon all borrowed objects
363         for (final PooledTestObject element : vec) {
364             element.setAbandoned(true);
365         }
366 
367         // Try launching a bunch of borrows concurrently.  Abandoned sweep will be triggered for each.
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         // Wait for all the threads to finish
376         for (int i = 0; i < CONCURRENT_BORROWS; i++) {
377             threads[i].join();
378         }
379 
380         // Return all objects that have not been destroyed
381         for (final PooledTestObject pto : vec) {
382             if (pto.isActive()) {
383                 pool.returnObject(pto);
384             }
385         }
386 
387         // Now, the number of active instances should be 0
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();  // Unregister pool created by setup
396         pool = new GenericObjectPool<>(
397              // validate takes 1 second
398              new SimpleFactory(0, 0),
399              new GenericObjectPoolConfig<>(), abandonedConfig);
400         pool.setTimeBetweenEvictionRuns(Duration.ofMillis(50));
401         // Borrow an object, wait long enough for it to be abandoned
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();  // Unregister pool created by setup
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      * Verify that an object that the evictor identifies as abandoned while it
420      * is in process of being returned to the pool is not destroyed.
421      *
422      * @throws Exception May occur in some failure modes
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();  // Unregister pool created by setup
430         pool = new GenericObjectPool<>(
431              // validate takes 1 second
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         // Borrow an object, wait long enough for it to be abandoned
440         // then arrange for evictor to run while it is being returned
441         // validation takes a second, evictor runs every 500 ms
442         final PooledTestObject obj = pool.borrowObject();
443         Thread.sleep(50);       // abandon obj
444         pool.returnObject(obj); // evictor will run during validation
445         final PooledTestObject obj2 = pool.borrowObject();
446         assertEquals(obj, obj2);          // should get original back
447         assertFalse(obj2.isDestroyed());  // and not destroyed
448     }
449 
450     /**
451      * JIRA: POOL-300
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      * Test case for https://issues.apache.org/jira/browse/DBCP-260.
473      * Borrow and abandon all the available objects then attempt to borrow one
474      * further object which should block until the abandoned objects are
475      * removed. We don't want the test to block indefinitely when it fails so
476      * use maxWait be check we don't actually have to wait that long.
477      *
478      * @throws Exception May occur in some failure modes
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") // This is going to be abandoned
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