| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.commons.pool2.impl; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.PrintWriter; |
| import java.lang.management.ManagementFactory; |
| import java.util.ArrayList; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| |
| import org.apache.commons.pool2.PooledObject; |
| import org.apache.commons.pool2.PooledObjectFactory; |
| import org.apache.commons.pool2.TrackedUse; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * TestCase for AbandonedObjectPool |
| */ |
| public class TestAbandonedObjectPool { |
| private GenericObjectPool<PooledTestObject> pool = null; |
| private AbandonedConfig abandonedConfig = null; |
| |
| @Before |
| public void setUp() throws Exception { |
| abandonedConfig = new AbandonedConfig(); |
| |
| // -- Uncomment the following line to enable logging -- |
| // abandonedConfig.setLogAbandoned(true); |
| |
| abandonedConfig.setRemoveAbandonedOnBorrow(true); |
| abandonedConfig.setRemoveAbandonedTimeout(1); |
| pool = new GenericObjectPool<>( |
| new SimpleFactory(), |
| new GenericObjectPoolConfig<PooledTestObject>(), |
| abandonedConfig); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| final String poolName = pool.getJmxName().toString(); |
| pool.clear(); |
| pool.close(); |
| pool = null; |
| |
| final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); |
| final Set<ObjectName> result = mbs.queryNames(new ObjectName( |
| "org.apache.commoms.pool2:type=GenericObjectPool,*"), null); |
| // There should be no registered pools at this point |
| final int registeredPoolCount = result.size(); |
| final StringBuilder msg = new StringBuilder("Current pool is: "); |
| msg.append(poolName); |
| msg.append(" Still open pools are: "); |
| for (final ObjectName name : result) { |
| // Clean these up ready for the next test |
| msg.append(name.toString()); |
| msg.append(" created via\n"); |
| msg.append(mbs.getAttribute(name, "CreationStackTrace")); |
| msg.append('\n'); |
| mbs.unregisterMBean(name); |
| } |
| Assert.assertEquals(msg.toString(), 0, registeredPoolCount); |
| } |
| |
| /** |
| * Tests fix for Bug 28579, a bug in AbandonedObjectPool that causes numActive to go negative |
| * in GenericObjectPool |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testConcurrentInvalidation() throws Exception { |
| final int POOL_SIZE = 30; |
| pool.setMaxTotal(POOL_SIZE); |
| pool.setMaxIdle(POOL_SIZE); |
| pool.setBlockWhenExhausted(false); |
| |
| // Exhaust the connection pool |
| final ArrayList<PooledTestObject> vec = new ArrayList<>(); |
| for (int i = 0; i < POOL_SIZE; i++) { |
| vec.add(pool.borrowObject()); |
| } |
| |
| // Abandon all borrowed objects |
| for (int i = 0; i < vec.size(); i++) { |
| vec.get(i).setAbandoned(true); |
| } |
| |
| // Try launching a bunch of borrows concurrently. Abandoned sweep will be triggered for each. |
| final int CONCURRENT_BORROWS = 5; |
| final Thread[] threads = new Thread[CONCURRENT_BORROWS]; |
| for (int i = 0; i < CONCURRENT_BORROWS; i++) { |
| threads[i] = new ConcurrentBorrower(vec); |
| threads[i].start(); |
| } |
| |
| // Wait for all the threads to finish |
| for (int i = 0; i < CONCURRENT_BORROWS; i++) { |
| threads[i].join(); |
| } |
| |
| // Return all objects that have not been destroyed |
| for (int i = 0; i < vec.size(); i++) { |
| final PooledTestObject pto = vec.get(i); |
| if (pto.isActive()) { |
| pool.returnObject(pto); |
| } |
| } |
| |
| // Now, the number of active instances should be 0 |
| Assert.assertTrue("numActive should have been 0, was " + pool.getNumActive(), pool.getNumActive() == 0); |
| } |
| |
| /** |
| * Verify that an object that gets flagged as abandoned and is subsequently returned |
| * is destroyed instead of being returned to the pool (and possibly later destroyed |
| * inappropriately). |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testAbandonedReturn() throws Exception { |
| abandonedConfig = new AbandonedConfig(); |
| abandonedConfig.setRemoveAbandonedOnBorrow(true); |
| abandonedConfig.setRemoveAbandonedTimeout(1); |
| pool.close(); // Unregister pool created by setup |
| pool = new GenericObjectPool<>( |
| new SimpleFactory(200, 0), |
| new GenericObjectPoolConfig<PooledTestObject>(), abandonedConfig); |
| final int n = 10; |
| pool.setMaxTotal(n); |
| pool.setBlockWhenExhausted(false); |
| PooledTestObject obj = null; |
| for (int i = 0; i < n - 2; i++) { |
| obj = pool.borrowObject(); |
| } |
| if (obj == null) { |
| throw new NullPointerException("Unable to borrow object from pool"); |
| } |
| final int deadMansHash = obj.hashCode(); |
| final ConcurrentReturner returner = new ConcurrentReturner(obj); |
| Thread.sleep(2000); // abandon checked out instances |
| // Now start a race - returner waits until borrowObject has kicked |
| // off removeAbandoned and then returns an instance that borrowObject |
| // will deem abandoned. Make sure it is not returned to the borrower. |
| returner.start(); // short delay, then return instance |
| Assert.assertTrue(pool.borrowObject().hashCode() != deadMansHash); |
| Assert.assertEquals(0, pool.getNumIdle()); |
| Assert.assertEquals(1, pool.getNumActive()); |
| } |
| |
| /** |
| * Verify that an object that gets flagged as abandoned and is subsequently |
| * invalidated is only destroyed (and pool counter decremented) once. |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testAbandonedInvalidate() throws Exception { |
| abandonedConfig = new AbandonedConfig(); |
| abandonedConfig.setRemoveAbandonedOnMaintenance(true); |
| abandonedConfig.setRemoveAbandonedTimeout(1); |
| pool.close(); // Unregister pool created by setup |
| pool = new GenericObjectPool<>( |
| // destroys take 200 ms |
| new SimpleFactory(200, 0), |
| new GenericObjectPoolConfig<PooledTestObject>(), abandonedConfig); |
| final int n = 10; |
| pool.setMaxTotal(n); |
| pool.setBlockWhenExhausted(false); |
| pool.setTimeBetweenEvictionRunsMillis(500); |
| PooledTestObject obj = null; |
| for (int i = 0; i < 5; i++) { |
| obj = pool.borrowObject(); |
| } |
| Thread.sleep(1000); // abandon checked out instances and let evictor start |
| pool.invalidateObject(obj); // Should not trigger another destroy / decrement |
| Thread.sleep(2000); // give evictor time to finish destroys |
| Assert.assertEquals(0, pool.getNumActive()); |
| Assert.assertEquals(5, pool.getDestroyedCount()); |
| } |
| |
| /** |
| * Verify that an object that the evictor identifies as abandoned while it |
| * is in process of being returned to the pool is not destroyed. |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testRemoveAbandonedWhileReturning() throws Exception { |
| abandonedConfig = new AbandonedConfig(); |
| abandonedConfig.setRemoveAbandonedOnMaintenance(true); |
| abandonedConfig.setRemoveAbandonedTimeout(1); |
| pool.close(); // Unregister pool created by setup |
| pool = new GenericObjectPool<>( |
| // validate takes 1 second |
| new SimpleFactory(0, 1000), |
| new GenericObjectPoolConfig<PooledTestObject>(), abandonedConfig); |
| final int n = 10; |
| pool.setMaxTotal(n); |
| pool.setBlockWhenExhausted(false); |
| pool.setTimeBetweenEvictionRunsMillis(500); |
| pool.setTestOnReturn(true); |
| // Borrow an object, wait long enough for it to be abandoned |
| // then arrange for evictor to run while it is being returned |
| // validation takes a second, evictor runs every 500 ms |
| final PooledTestObject obj = pool.borrowObject(); |
| Thread.sleep(50); // abandon obj |
| pool.returnObject(obj); // evictor will run during validation |
| final PooledTestObject obj2 = pool.borrowObject(); |
| Assert.assertEquals(obj, obj2); // should get original back |
| Assert.assertTrue(!obj2.isDestroyed()); // and not destroyed |
| } |
| |
| /** |
| * Test case for https://issues.apache.org/jira/browse/DBCP-260. |
| * Borrow and abandon all the available objects then attempt to borrow one |
| * further object which should block until the abandoned objects are |
| * removed. We don't want the test to block indefinitely when it fails so |
| * use maxWait be check we don't actually have to wait that long. |
| * |
| * @throws Exception May occur in some failure modes |
| */ |
| @Test |
| public void testWhenExhaustedBlock() throws Exception { |
| abandonedConfig.setRemoveAbandonedOnMaintenance(true); |
| pool.setAbandonedConfig(abandonedConfig); |
| pool.setTimeBetweenEvictionRunsMillis(500); |
| |
| pool.setMaxTotal(1); |
| |
| @SuppressWarnings("unused") // This is going to be abandoned |
| final |
| PooledTestObject o1 = pool.borrowObject(); |
| |
| final long start = System.currentTimeMillis(); |
| final PooledTestObject o2 = pool.borrowObject(5000); |
| final long end = System.currentTimeMillis(); |
| |
| pool.returnObject(o2); |
| |
| Assert.assertTrue (end - start < 5000); |
| } |
| |
| /** |
| * JIRA: POOL-300 |
| */ |
| @Test |
| public void testStackTrace() throws Exception { |
| abandonedConfig.setRemoveAbandonedOnMaintenance(true); |
| abandonedConfig.setLogAbandoned(true); |
| abandonedConfig.setRemoveAbandonedTimeout(1); |
| final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| final BufferedOutputStream bos = new BufferedOutputStream(baos); |
| final PrintWriter pw = new PrintWriter(bos); |
| abandonedConfig.setLogWriter(pw); |
| pool.setAbandonedConfig(abandonedConfig); |
| pool.setTimeBetweenEvictionRunsMillis(100); |
| final PooledTestObject o1 = pool.borrowObject(); |
| Thread.sleep(2000); |
| Assert.assertTrue(o1.isDestroyed()); |
| bos.flush(); |
| Assert.assertTrue(baos.toString().indexOf("Pooled object") >= 0); |
| } |
| |
| class ConcurrentBorrower extends Thread { |
| private final ArrayList<PooledTestObject> _borrowed; |
| |
| public ConcurrentBorrower(final ArrayList<PooledTestObject> borrowed) { |
| _borrowed = borrowed; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| _borrowed.add(pool.borrowObject()); |
| } catch (final Exception e) { |
| // expected in most cases |
| } |
| } |
| } |
| |
| class ConcurrentReturner extends Thread { |
| private final PooledTestObject returned; |
| public ConcurrentReturner(final PooledTestObject obj) { |
| returned = obj; |
| } |
| @Override |
| public void run() { |
| try { |
| sleep(20); |
| pool.returnObject(returned); |
| } catch (final Exception e) { |
| // ignore |
| } |
| } |
| } |
| |
| private static class SimpleFactory implements PooledObjectFactory<PooledTestObject> { |
| |
| private final long destroyLatency; |
| private final long validateLatency; |
| |
| public SimpleFactory() { |
| destroyLatency = 0; |
| validateLatency = 0; |
| } |
| |
| public SimpleFactory(final long destroyLatency, final long validateLatency) { |
| this.destroyLatency = destroyLatency; |
| this.validateLatency = validateLatency; |
| } |
| |
| @Override |
| public PooledObject<PooledTestObject> makeObject() { |
| return new DefaultPooledObject<>(new PooledTestObject()); |
| } |
| |
| @Override |
| public boolean validateObject(final PooledObject<PooledTestObject> obj) { |
| try { |
| Thread.sleep(validateLatency); |
| } catch (final Exception ex) { |
| // ignore |
| } |
| return true; |
| } |
| |
| @Override |
| public void activateObject(final PooledObject<PooledTestObject> obj) { |
| obj.getObject().setActive(true); |
| } |
| |
| @Override |
| public void passivateObject(final PooledObject<PooledTestObject> obj) { |
| obj.getObject().setActive(false); |
| } |
| |
| @Override |
| public void destroyObject(final PooledObject<PooledTestObject> obj) throws Exception { |
| obj.getObject().setActive(false); |
| // while destroying instances, yield control to other threads |
| // helps simulate threading errors |
| Thread.yield(); |
| if (destroyLatency != 0) { |
| Thread.sleep(destroyLatency); |
| } |
| obj.getObject().destroy(); |
| } |
| } |
| } |
| |
| class PooledTestObject implements TrackedUse { |
| private boolean active = false; |
| private boolean destroyed = false; |
| private int _hash = 0; |
| private boolean _abandoned = false; |
| private static final AtomicInteger hash = new AtomicInteger(); |
| |
| public PooledTestObject() { |
| _hash = hash.incrementAndGet(); |
| } |
| |
| public synchronized void setActive(final boolean b) { |
| active = b; |
| } |
| |
| public synchronized boolean isActive() { |
| return active; |
| } |
| |
| public void destroy() { |
| destroyed = true; |
| } |
| |
| public boolean isDestroyed() { |
| return destroyed; |
| } |
| |
| @Override |
| public int hashCode() { |
| return _hash; |
| } |
| |
| public void setAbandoned(final boolean b) { |
| _abandoned = b; |
| } |
| |
| @Override |
| public long getLastUsed() { |
| if (_abandoned) { |
| // Abandoned object sweep will occur no matter what the value of removeAbandonedTimeout, |
| // because this indicates that this object was last used decades ago |
| return 1; |
| } |
| // Abandoned object sweep won't clean up this object |
| return 0; |
| } |
| |
| @Override |
| public boolean equals(final Object obj) { |
| if (!(obj instanceof PooledTestObject)) { |
| return false; |
| } |
| return obj.hashCode() == hashCode(); |
| } |
| } |
| |