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.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  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      public PooledTestObject() {
58          this.hash = ATOMIC_HASH.incrementAndGet();
59      }
60  
61      public 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     public synchronized boolean isActive() {
104         return active;
105     }
106 
107     public boolean isDestroyed() {
108         return destroyed;
109     }
110 
111     public boolean isDetached() {
112         return detached;
113     }
114 
115     public void setAbandoned(final boolean b) {
116         abandoned = b;
117     }
118 
119     public synchronized void setActive(final boolean b) {
120         active = b;
121     }
122 }
123 
124 /**
125  * TestCase for AbandonedObjectPool
126  */
127 public class TestAbandonedObjectPool {
128 
129     class ConcurrentBorrower extends Thread {
130         private final ArrayList<PooledTestObject> borrowed;
131 
132         public 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     class ConcurrentReturner extends Thread {
146         private final PooledTestObject returned;
147         public ConcurrentReturner(final PooledTestObject obj) {
148             returned = obj;
149         }
150         @Override
151         public void run() {
152             try {
153                 sleep(20);
154                 pool.returnObject(returned);
155             } catch (final Exception e) {
156                 // ignore
157             }
158         }
159     }
160 
161     private static class SimpleFactory implements PooledObjectFactory<PooledTestObject> {
162 
163         private final long destroyLatency;
164         private final long validateLatency;
165 
166         public SimpleFactory() {
167             destroyLatency = 0;
168             validateLatency = 0;
169         }
170 
171         public SimpleFactory(final long destroyLatency, final long validateLatency) {
172             this.destroyLatency = destroyLatency;
173             this.validateLatency = validateLatency;
174         }
175 
176         @Override
177         public void activateObject(final PooledObject<PooledTestObject> obj) {
178             obj.getObject().setActive(true);
179         }
180 
181         @Override
182         public void destroyObject(final PooledObject<PooledTestObject> obj) throws InterruptedException {
183             destroyObject(obj, DestroyMode.NORMAL);
184         }
185 
186         @Override
187         public void destroyObject(final PooledObject<PooledTestObject> obj, final DestroyMode destroyMode) throws InterruptedException {
188             obj.getObject().setActive(false);
189             // while destroying instances, yield control to other threads
190             // helps simulate threading errors
191             Thread.yield();
192             if (destroyLatency != 0) {
193                 Thread.sleep(destroyLatency);
194             }
195             obj.getObject().destroy(destroyMode);
196         }
197 
198         @Override
199         public PooledObject<PooledTestObject> makeObject() {
200             return new DefaultPooledObject<>(new PooledTestObject());
201         }
202 
203         @Override
204         public void passivateObject(final PooledObject<PooledTestObject> obj) {
205             obj.getObject().setActive(false);
206         }
207 
208         @Override
209         public boolean validateObject(final PooledObject<PooledTestObject> obj) {
210             Waiter.sleepQuietly(validateLatency);
211             return true;
212         }
213     }
214 
215     private GenericObjectPool<PooledTestObject> pool;
216 
217     private AbandonedConfig abandonedConfig;
218 
219     @SuppressWarnings("deprecation")
220     @BeforeEach
221     public void setUp() {
222         abandonedConfig = new AbandonedConfig();
223 
224         // Uncomment the following line to enable logging:
225         // abandonedConfig.setLogAbandoned(true);
226 
227         abandonedConfig.setRemoveAbandonedOnBorrow(true);
228         // One second Duration.
229         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
230         assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
231         assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout()); // in seconds.
232         // One second int (not millis).
233         abandonedConfig.setRemoveAbandonedTimeout(1);
234         assertEquals(TestConstants.ONE_SECOND_DURATION, abandonedConfig.getRemoveAbandonedTimeoutDuration());
235         assertEquals(1, abandonedConfig.getRemoveAbandonedTimeout()); // in seconds.
236 
237         pool = new GenericObjectPool<>(
238                new SimpleFactory(),
239                new GenericObjectPoolConfig<>(),
240                abandonedConfig);
241     }
242 
243     @AfterEach
244     public void tearDown() throws Exception {
245         final ObjectName jmxName = pool.getJmxName();
246         final String poolName = Objects.toString(jmxName, null);
247         pool.clear();
248         pool.close();
249         pool = null;
250 
251         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
252         final Set<ObjectName> result = mbs.queryNames(new ObjectName(
253                 "org.apache.commoms.pool2:type=GenericObjectPool,*"), null);
254         // There should be no registered pools at this point
255         final int registeredPoolCount = result.size();
256         final StringBuilder msg = new StringBuilder("Current pool is: ");
257         msg.append(poolName);
258         msg.append("  Still open pools are: ");
259         for (final ObjectName name : result) {
260             // Clean these up ready for the next test
261             msg.append(name.toString());
262             msg.append(" created via\n");
263             msg.append(mbs.getAttribute(name, "CreationStackTrace"));
264             msg.append('\n');
265             mbs.unregisterMBean(name);
266         }
267         assertEquals(0, registeredPoolCount, msg.toString());
268     }
269 
270     /**
271      * Verify that an object that gets flagged as abandoned and is subsequently
272      * invalidated is only destroyed (and pool counter decremented) once.
273      *
274      * @throws Exception May occur in some failure modes
275      */
276     @Test
277     public void testAbandonedInvalidate() throws Exception {
278         abandonedConfig = new AbandonedConfig();
279         abandonedConfig.setRemoveAbandonedOnMaintenance(true);
280         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
281         pool.close();  // Unregister pool created by setup
282         pool = new GenericObjectPool<>(
283                 // destroys take 200 ms
284                 new SimpleFactory(200, 0),
285                 new GenericObjectPoolConfig<>(), abandonedConfig);
286         final int n = 10;
287         pool.setMaxTotal(n);
288         pool.setBlockWhenExhausted(false);
289         pool.setDurationBetweenEvictionRuns(Duration.ofMillis(500));
290         PooledTestObject obj = null;
291         for (int i = 0; i < 5; i++) {
292             obj = pool.borrowObject();
293         }
294         Thread.sleep(1000);          // abandon checked out instances and let evictor start
295         pool.invalidateObject(obj);  // Should not trigger another destroy / decrement
296         Thread.sleep(2000);          // give evictor time to finish destroys
297         assertEquals(0, pool.getNumActive());
298         assertEquals(5, pool.getDestroyedCount());
299     }
300 
301     /**
302      * Verify that an object that gets flagged as abandoned and is subsequently returned
303      * is destroyed instead of being returned to the pool (and possibly later destroyed
304      * inappropriately).
305      *
306      * @throws Exception May occur in some failure modes
307      */
308     @Test
309     public void testAbandonedReturn() throws Exception {
310         abandonedConfig = new AbandonedConfig();
311         abandonedConfig.setRemoveAbandonedOnBorrow(true);
312         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
313         pool.close();  // Unregister pool created by setup
314         pool = new GenericObjectPool<>(
315                 new SimpleFactory(200, 0),
316                 new GenericObjectPoolConfig<>(), abandonedConfig);
317         final int n = 10;
318         pool.setMaxTotal(n);
319         pool.setBlockWhenExhausted(false);
320         PooledTestObject obj = null;
321         for (int i = 0; i < n - 2; i++) {
322             obj = pool.borrowObject();
323         }
324         Objects.requireNonNull(obj, "Unable to borrow object from pool");
325         final int deadMansHash = obj.hashCode();
326         final ConcurrentReturner returner = new ConcurrentReturner(obj);
327         Thread.sleep(2000);  // abandon checked out instances
328         // Now start a race - returner waits until borrowObject has kicked
329         // off removeAbandoned and then returns an instance that borrowObject
330         // will deem abandoned.  Make sure it is not returned to the borrower.
331         returner.start();    // short delay, then return instance
332         assertTrue(pool.borrowObject().hashCode() != deadMansHash);
333         assertEquals(0, pool.getNumIdle());
334         assertEquals(1, pool.getNumActive());
335     }
336 
337     /**
338      * Tests fix for Bug 28579, a bug in AbandonedObjectPool that causes numActive to go negative
339      * in GenericObjectPool
340      *
341      * @throws Exception May occur in some failure modes
342      */
343     @Test
344     public void testConcurrentInvalidation() throws Exception {
345         final int POOL_SIZE = 30;
346         pool.setMaxTotal(POOL_SIZE);
347         pool.setMaxIdle(POOL_SIZE);
348         pool.setBlockWhenExhausted(false);
349 
350         // Exhaust the connection pool
351         final ArrayList<PooledTestObject> vec = new ArrayList<>();
352         for (int i = 0; i < POOL_SIZE; i++) {
353             vec.add(pool.borrowObject());
354         }
355 
356         // Abandon all borrowed objects
357         for (final PooledTestObject element : vec) {
358             element.setAbandoned(true);
359         }
360 
361         // Try launching a bunch of borrows concurrently.  Abandoned sweep will be triggered for each.
362         final int CONCURRENT_BORROWS = 5;
363         final Thread[] threads = new Thread[CONCURRENT_BORROWS];
364         for (int i = 0; i < CONCURRENT_BORROWS; i++) {
365             threads[i] = new ConcurrentBorrower(vec);
366             threads[i].start();
367         }
368 
369         // Wait for all the threads to finish
370         for (int i = 0; i < CONCURRENT_BORROWS; i++) {
371             threads[i].join();
372         }
373 
374         // Return all objects that have not been destroyed
375         for (final PooledTestObject pto : vec) {
376             if (pto.isActive()) {
377                 pool.returnObject(pto);
378             }
379         }
380 
381         // Now, the number of active instances should be 0
382         assertEquals(0, pool.getNumActive(), "numActive should have been 0, was " + pool.getNumActive());
383     }
384 
385     public void testDestroyModeAbandoned() throws Exception {
386         abandonedConfig = new AbandonedConfig();
387         abandonedConfig.setRemoveAbandonedOnMaintenance(true);
388         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
389         pool.close();  // Unregister pool created by setup
390         pool = new GenericObjectPool<>(
391              // validate takes 1 second
392              new SimpleFactory(0, 0),
393              new GenericObjectPoolConfig<>(), abandonedConfig);
394         pool.setTimeBetweenEvictionRuns(Duration.ofMillis(50));
395         // Borrow an object, wait long enough for it to be abandoned
396         final PooledTestObject obj = pool.borrowObject();
397         Thread.sleep(100);
398         assertTrue(obj.isDetached());
399     }
400 
401     public void testDestroyModeNormal() throws Exception {
402         abandonedConfig = new AbandonedConfig();
403         pool.close();  // Unregister pool created by setup
404         pool = new GenericObjectPool<>(new SimpleFactory(0, 0));
405         pool.setMaxIdle(0);
406         final PooledTestObject obj = pool.borrowObject();
407         pool.returnObject(obj);
408         assertTrue(obj.isDestroyed());
409         assertFalse(obj.isDetached());
410     }
411 
412     /**
413      * Verify that an object that the evictor identifies as abandoned while it
414      * is in process of being returned to the pool is not destroyed.
415      *
416      * @throws Exception May occur in some failure modes
417      */
418     @Test
419     public void testRemoveAbandonedWhileReturning() throws Exception {
420         abandonedConfig = new AbandonedConfig();
421         abandonedConfig.setRemoveAbandonedOnMaintenance(true);
422         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
423         pool.close();  // Unregister pool created by setup
424         pool = new GenericObjectPool<>(
425              // validate takes 1 second
426              new SimpleFactory(0, 1000),
427              new GenericObjectPoolConfig<>(), abandonedConfig);
428         final int n = 10;
429         pool.setMaxTotal(n);
430         pool.setBlockWhenExhausted(false);
431         pool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
432         pool.setTestOnReturn(true);
433         // Borrow an object, wait long enough for it to be abandoned
434         // then arrange for evictor to run while it is being returned
435         // validation takes a second, evictor runs every 500 ms
436         final PooledTestObject obj = pool.borrowObject();
437         Thread.sleep(50);       // abandon obj
438         pool.returnObject(obj); // evictor will run during validation
439         final PooledTestObject obj2 = pool.borrowObject();
440         assertEquals(obj, obj2);          // should get original back
441         assertFalse(obj2.isDestroyed());  // and not destroyed
442     }
443 
444     /**
445      * JIRA: POOL-300
446      */
447     @Test
448     public void testStackTrace() throws Exception {
449         abandonedConfig.setRemoveAbandonedOnMaintenance(true);
450         abandonedConfig.setLogAbandoned(true);
451         abandonedConfig.setRemoveAbandonedTimeout(TestConstants.ONE_SECOND_DURATION);
452         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
453         final BufferedOutputStream bos = new BufferedOutputStream(baos);
454         final PrintWriter pw = new PrintWriter(bos);
455         abandonedConfig.setLogWriter(pw);
456         pool.setAbandonedConfig(abandonedConfig);
457         pool.setTimeBetweenEvictionRuns(Duration.ofMillis(100));
458         final PooledTestObject o1 = pool.borrowObject();
459         Thread.sleep(2000);
460         assertTrue(o1.isDestroyed());
461         bos.flush();
462         assertTrue(baos.toString().indexOf("Pooled object") >= 0);
463     }
464 
465     /**
466      * Test case for https://issues.apache.org/jira/browse/DBCP-260.
467      * Borrow and abandon all the available objects then attempt to borrow one
468      * further object which should block until the abandoned objects are
469      * removed. We don't want the test to block indefinitely when it fails so
470      * use maxWait be check we don't actually have to wait that long.
471      *
472      * @throws Exception May occur in some failure modes
473      */
474     @Test
475     public void testWhenExhaustedBlock() throws Exception {
476         abandonedConfig.setRemoveAbandonedOnMaintenance(true);
477         pool.setAbandonedConfig(abandonedConfig);
478         pool.setTimeBetweenEvictionRuns(Duration.ofMillis(500));
479 
480         pool.setMaxTotal(1);
481 
482         @SuppressWarnings("unused") // This is going to be abandoned
483         final PooledTestObject o1 = pool.borrowObject();
484 
485         final long startMillis = System.currentTimeMillis();
486         final PooledTestObject o2 = pool.borrowObject(5000);
487         final long endMillis = System.currentTimeMillis();
488 
489         pool.returnObject(o2);
490 
491         assertTrue(endMillis - startMillis < 5000);
492     }
493 }
494