Author: psteitz Date: Tue Feb 10 02:49:54 2015 New Revision: 1658616 URL: http://svn.apache.org/r1658616 Log: Fixed connection leak when SQLException is thrown while enlisting an XA transaction. JIRA: DBCP-433 Reported (with patch) by Vladimir Konkov Test case written by Thomas Neidhart
Added: commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/managed/TestManagedConnection.java (with props) Modified: commons/proper/dbcp/trunk/src/changes/changes.xml commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java Modified: commons/proper/dbcp/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/changes/changes.xml?rev=1658616&r1=1658615&r2=1658616&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/changes/changes.xml (original) +++ commons/proper/dbcp/trunk/src/changes/changes.xml Tue Feb 10 02:49:54 2015 @@ -103,6 +103,10 @@ The <action> type attribute can be add,u Changed BasicDataSource createDataSource method to ensure that initialization completes before clients get reference to newly created instances. </action> + <action issue="DBCP-433" dev="psteitz" type="fix" due-to="Vladimir Konkov"> + Fixed connection leak when SQLException is thrown while enlisting an XA + transaction. + </action> </release> <release version="2.0.1" date="24 May 2014" description="This is a bug fix release."> <action dev="markt" type="fix"> Modified: commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java?rev=1658616&r1=1658615&r2=1658616&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java (original) +++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/managed/ManagedConnection.java Tue Feb 10 02:49:54 2015 @@ -119,11 +119,12 @@ public class ManagedConnection<C extends // transaction completes isSharedConnection = true; } else { + C connection = getDelegateInternal(); // if our delegate is null, create one - if (getDelegateInternal() == null) { + if (connection == null) { try { // borrow a new connection from the pool - C connection = pool.borrowObject(); + connection = pool.borrowObject(); setDelegate(connection); } catch (Exception e) { throw new SQLException("Unable to acquire a new connection from the pool", e); @@ -137,10 +138,15 @@ public class ManagedConnection<C extends // register our connection as the shared connection try { - transactionContext.setSharedConnection(getDelegateInternal()); + transactionContext.setSharedConnection(connection); } catch (SQLException e) { // transaction is hosed transactionContext = null; + try { + pool.invalidateObject(connection); + } catch (Exception e1) { + // we are try but no luck + } throw e; } } Added: commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/managed/TestManagedConnection.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/managed/TestManagedConnection.java?rev=1658616&view=auto ============================================================================== --- commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/managed/TestManagedConnection.java (added) +++ commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/managed/TestManagedConnection.java Tue Feb 10 02:49:54 2015 @@ -0,0 +1,213 @@ +/** + * + * 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.dbcp2.managed; + +import static org.junit.Assert.*; + +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.RollbackException; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.TransactionManager; +import javax.transaction.xa.XAResource; + +import org.apache.commons.dbcp2.ConnectionFactory; +import org.apache.commons.dbcp2.DelegatingConnection; +import org.apache.commons.dbcp2.DriverConnectionFactory; +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.dbcp2.PoolableConnectionFactory; +import org.apache.commons.dbcp2.PoolingDataSource; +import org.apache.commons.dbcp2.TesterDriver; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.geronimo.transaction.manager.TransactionManagerImpl; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * TestSuite for ManagedConnection. + */ +public class TestManagedConnection { + + protected PoolingDataSource<PoolableConnection> ds = null; + + private GenericObjectPool<PoolableConnection> pool = null; + + protected TransactionManager transactionManager; + + @Before + public void setUp() + throws Exception { + // create a GeronimoTransactionManager for testing + transactionManager = new TransactionManagerImpl(); + + // create a driver connection factory + Properties properties = new Properties(); + properties.setProperty("user", "username"); + properties.setProperty("password", "password"); + ConnectionFactory connectionFactory = new DriverConnectionFactory(new TesterDriver(), "jdbc:apache:commons:testdriver", properties); + + // wrap it with a LocalXAConnectionFactory + XAConnectionFactory xaConnectionFactory = new UncooperativeLocalXAConnectionFactory(transactionManager, connectionFactory); + + // create the pool object factory + PoolableConnectionFactory factory = new PoolableConnectionFactory(xaConnectionFactory, null); + factory.setValidationQuery("SELECT DUMMY FROM DUAL"); + factory.setDefaultReadOnly(Boolean.TRUE); + factory.setDefaultAutoCommit(Boolean.TRUE); + + // create the pool + pool = new GenericObjectPool<>(factory); + factory.setPool(pool); + pool.setMaxTotal(10); + pool.setMaxWaitMillis(100); + + // finally create the datasource + ds = new ManagedDataSource<>(pool, xaConnectionFactory.getTransactionRegistry()); + ds.setAccessToUnderlyingConnectionAllowed(true); + } + + @After + public void tearDown() + throws Exception { + pool.close(); + } + + public Connection getConnection() + throws Exception { + return ds.getConnection(); + } + + @Test + public void testConnectionReturnOnErrorWhenEnlistingXAResource() + throws Exception { + // see DBCP-433 + + transactionManager.begin(); + try { + DelegatingConnection<?> connectionA = (DelegatingConnection<?>) getConnection(); + connectionA.close(); + } catch (SQLException e) { + // expected + } + transactionManager.commit(); + assertEquals(1, pool.getBorrowedCount()); + // assertEquals(1, pool.getReturnedCount()); + assertEquals(1, pool.getDestroyedCount()); + assertEquals(0, pool.getNumActive()); + } + + private class UncooperativeLocalXAConnectionFactory + extends LocalXAConnectionFactory { + + public UncooperativeLocalXAConnectionFactory(TransactionManager transactionManager, ConnectionFactory connectionFactory) { + super(transactionManager, connectionFactory); + + try { + // inject our own TransactionRegistry which returns Uncooperative Transactions which always fail to enlist a XAResource + Field field = LocalXAConnectionFactory.class.getDeclaredField("transactionRegistry"); + field.setAccessible(true); + field.set(this, new UncooperativeTransactionRegistry(transactionManager)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private class UncooperativeTransactionRegistry + extends TransactionRegistry { + + public UncooperativeTransactionRegistry(TransactionManager transactionManager) { + super(transactionManager); + } + + @Override + public TransactionContext getActiveTransactionContext() + throws SQLException { + try { + return new TransactionContext(this, new UncooperativeTransaction(transactionManager.getTransaction())); + } catch (SystemException e) { + return null; + } + } + + } + + /** + * Transaction that always fails enlistResource. + */ + private class UncooperativeTransaction + implements Transaction { + + private Transaction wrappedTransaction; + + public UncooperativeTransaction(Transaction transaction) { + this.wrappedTransaction = transaction; + } + + @Override + public void commit() + throws HeuristicMixedException, HeuristicRollbackException, RollbackException, SecurityException, + SystemException { + wrappedTransaction.commit(); + } + + @Override + public boolean delistResource(XAResource arg0, int arg1) + throws IllegalStateException, SystemException { + return wrappedTransaction.delistResource(arg0, arg1); + } + + @Override + public int getStatus() + throws SystemException { + return wrappedTransaction.getStatus(); + } + + @Override + public void registerSynchronization(Synchronization arg0) + throws IllegalStateException, RollbackException, SystemException { + wrappedTransaction.registerSynchronization(arg0); + } + + @Override + public void rollback() + throws IllegalStateException, SystemException { + wrappedTransaction.rollback(); + } + + @Override + public void setRollbackOnly() + throws IllegalStateException, SystemException { + wrappedTransaction.setRollbackOnly(); + } + + @Override + public synchronized boolean enlistResource(XAResource xaRes) { + return false; + } + } + +} Propchange: commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/managed/TestManagedConnection.java ------------------------------------------------------------------------------ svn:eol-style = native