On 30 January 2014 21:54, <ma...@apache.org> wrote: > Author: markt > Date: Thu Jan 30 21:54:56 2014 > New Revision: 1562992 > > URL: http://svn.apache.org/r1562992 > Log: > Fix DBCP-369 > Fix threading issue when using multiple instances of the SharedPoolDataSource > concurrently. > I can't reproduce this but the issue can be seen from looking at the code. > > Modified: > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/changes/changes.xml > > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/main/java/org/apache/commons/dbcp/datasources/InstanceKeyObjectFactory.java > > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/test/java/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java > > Modified: > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/changes/changes.xml > URL: > http://svn.apache.org/viewvc/commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/changes/changes.xml?rev=1562992&r1=1562991&r2=1562992&view=diff > ============================================================================== > --- commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/changes/changes.xml > (original) > +++ commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/changes/changes.xml > Thu Jan 30 21:54:56 2014 > @@ -74,6 +74,10 @@ The <action> type attribute can be add,u > Allow accessToUnderlyingConnectionAllowed to be configured when > configuration takes place via JNDI in a JavaEE container. > </action> > + <action dev="markt" issue="DBCP-369" type="fix" due-to="Michael > Pradel"> > + Fix threading issue when using multiple instances of the > + SharedPoolDataSource concurrently. > + </action> > </release> > <release version="1.4.1" date="TBD" description="TBD"> > <action dev="psteitz" issue="DBCP-367" type="fix" due-to="Ken > Tatsushita"> > > Modified: > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/main/java/org/apache/commons/dbcp/datasources/InstanceKeyObjectFactory.java > URL: > http://svn.apache.org/viewvc/commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/main/java/org/apache/commons/dbcp/datasources/InstanceKeyObjectFactory.java?rev=1562992&r1=1562991&r2=1562992&view=diff > ============================================================================== > --- > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/main/java/org/apache/commons/dbcp/datasources/InstanceKeyObjectFactory.java > (original) > +++ > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/main/java/org/apache/commons/dbcp/datasources/InstanceKeyObjectFactory.java > Thu Jan 30 21:54:56 2014 > @@ -14,7 +14,6 @@ > * See the License for the specific language governing permissions and > * limitations under the License. > */ > - > package org.apache.commons.dbcp.datasources; > > import java.io.ByteArrayInputStream; > @@ -23,9 +22,9 @@ import java.io.ObjectInputStream; > > import java.util.Hashtable; > import java.util.Map; > -import java.util.HashMap; > import java.util.Iterator; > import java.util.Properties; > +import java.util.concurrent.ConcurrentHashMap; > > import javax.naming.Context; > import javax.naming.Name; > @@ -39,10 +38,9 @@ import javax.naming.spi.ObjectFactory; > * > * @version $Revision$ $Date$ > */ > -abstract class InstanceKeyObjectFactory > - implements ObjectFactory > -{ > - private static final Map instanceMap = new HashMap(); > +abstract class InstanceKeyObjectFactory implements ObjectFactory { > + > + private static final Map instanceMap = new ConcurrentHashMap();
Does not work - HashMap allows null as a key, but ConcurrentHashMap does not. And the key can be null - not sure whether that is a feaure of the unit tests or if it is allowed for regular use as well. > synchronized static String registerNewInstance(InstanceKeyDataSource ds) > { > int max = 0; > > Modified: > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/test/java/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java > URL: > http://svn.apache.org/viewvc/commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/test/java/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java?rev=1562992&r1=1562991&r2=1562992&view=diff > ============================================================================== > --- > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/test/java/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java > (original) > +++ > commons/proper/dbcp/branches/DBCP_1_5_x_BRANCH/src/test/java/org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java > Thu Jan 30 21:54:56 2014 > @@ -5,9 +5,9 @@ > * 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. > @@ -21,6 +21,7 @@ import java.sql.Connection; > import java.sql.PreparedStatement; > import java.sql.ResultSet; > import java.sql.SQLException; > +import java.util.ArrayList; > > import javax.sql.DataSource; > > @@ -79,14 +80,14 @@ public class TestSharedPoolDataSource ex > > /** > * Switching 'u1 -> 'u2' and 'p1' -> 'p2' will > - * exhibit the bug detailed in > + * exhibit the bug detailed in > * http://issues.apache.org/bugzilla/show_bug.cgi?id=18905 > - * > + * > * Starting with a successful connection, then incorrect password, > * then correct password for same user illustrates > * JIRA: DBCP-245 > */ > - public void testIncorrectPassword() throws Exception > + public void testIncorrectPassword() throws Exception > { > ds.getConnection("u2", "p2").close(); > try { > @@ -97,26 +98,26 @@ public class TestSharedPoolDataSource ex > // should fail > > } > - > + > // Use good password > ds.getConnection("u1", "p1").close(); > - try > + try > { > ds.getConnection("u1", "x"); > fail("Able to retrieve connection with incorrect password"); > } > catch (SQLException e) > { > - if (!e.getMessage().startsWith("Given password did not match")) > + if (!e.getMessage().startsWith("Given password did not match")) > { > throw e; > } > // else the exception was expected > } > - > + > // Make sure we can still use our good password. > ds.getConnection("u1", "p1").close(); > - > + > // Try related users and passwords > ds.getConnection("foo", "bar").close(); > try { > @@ -132,7 +133,7 @@ public class TestSharedPoolDataSource ex > } > > > - public void testSimple() throws Exception > + public void testSimple() throws Exception > { > Connection conn = ds.getConnection(); > assertNotNull(conn); > @@ -146,7 +147,7 @@ public class TestSharedPoolDataSource ex > conn.close(); > } > > - public void testSimpleWithUsername() throws Exception > + public void testSimpleWithUsername() throws Exception > { > Connection conn = ds.getConnection("u1", "p1"); > assertNotNull(conn); > @@ -160,12 +161,12 @@ public class TestSharedPoolDataSource ex > conn.close(); > } > > - public void testClosingWithUserName() > - throws Exception > + public void testClosingWithUserName() > + throws Exception > { > Connection[] c = new Connection[getMaxActive()]; > // open the maximum connections > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i] = ds.getConnection("u1", "p1"); > } > @@ -176,29 +177,29 @@ public class TestSharedPoolDataSource ex > // get a new connection > c[0] = ds.getConnection("u1", "p1"); > > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i].close(); > } > > // open the maximum connections > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i] = ds.getConnection("u1", "p1"); > } > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i].close(); > } > } > > - public void testSimple2() > - throws Exception > + public void testSimple2() > + throws Exception > { > Connection conn = ds.getConnection(); > assertNotNull(conn); > > - PreparedStatement stmt = > + PreparedStatement stmt = > conn.prepareStatement("select * from dual"); > assertNotNull(stmt); > ResultSet rset = stmt.executeQuery(); > @@ -206,7 +207,7 @@ public class TestSharedPoolDataSource ex > assertTrue(rset.next()); > rset.close(); > stmt.close(); > - > + > stmt = conn.prepareStatement("select * from dual"); > assertNotNull(stmt); > rset = stmt.executeQuery(); > @@ -214,14 +215,14 @@ public class TestSharedPoolDataSource ex > assertTrue(rset.next()); > rset.close(); > stmt.close(); > - > + > conn.close(); > - try > + try > { > conn.createStatement(); > fail("Can't use closed connections"); > - } > - catch(SQLException e) > + } > + catch(SQLException e) > { > // expected > } > @@ -244,38 +245,38 @@ public class TestSharedPoolDataSource ex > assertTrue(rset.next()); > rset.close(); > stmt.close(); > - > + > conn.close(); > conn = null; > } > > - public void testOpening() > - throws Exception > + public void testOpening() > + throws Exception > { > Connection[] c = new Connection[getMaxActive()]; > // test that opening new connections is not closing previous > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i] = ds.getConnection(); > assertTrue(c[i] != null); > - for (int j=0; j<=i; j++) > + for (int j=0; j<=i; j++) > { > assertTrue(!c[j].isClosed()); > } > } > > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i].close(); > } > } > > - public void testClosing() > - throws Exception > + public void testClosing() > + throws Exception > { > Connection[] c = new Connection[getMaxActive()]; > // open the maximum connections > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i] = ds.getConnection(); > } > @@ -283,36 +284,36 @@ public class TestSharedPoolDataSource ex > // close one of the connections > c[0].close(); > assertTrue(c[0].isClosed()); > - > + > // get a new connection > c[0] = ds.getConnection(); > > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i].close(); > } > } > - > + > /** > * Test pool close. Illustrates BZ 37359. > - * > + * > * @throws Exception > */ > public void testClosePool() throws Exception { > ((SharedPoolDataSource)ds).close(); > SharedPoolDataSource tds = new SharedPoolDataSource(); > - // NPE before BZ 37359 fix > + // NPE before BZ 37359 fix > tds.close(); > } > > - public void testMaxActive() > - throws Exception > + public void testMaxActive() > + throws Exception > { > Connection[] c = new Connection[getMaxActive()]; > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i] = ds.getConnection(); > - assertTrue(c[i] != null); > + assertTrue(c[i] != null); > } > > try > @@ -326,49 +327,49 @@ public class TestSharedPoolDataSource ex > // throw an exception > } > > - for (int i=0; i<c.length; i++) > + for (int i=0; i<c.length; i++) > { > c[i].close(); > } > } > - > + > public void testMaxWait() throws Exception { > final int maxWait = 1000; > final int theadCount = 20; > - > + > ((SharedPoolDataSource)ds).setMaxWait(maxWait); > // Obtain all the connections from the pool > Connection[] c = new Connection[getMaxActive()]; > for (int i=0; i<c.length; i++) { > c[i] = ds.getConnection("foo","bar"); > - assertTrue(c[i] != null); > + assertTrue(c[i] != null); > } > > long start = System.currentTimeMillis(); > - > + > // Run a thread test with minimal hold time > // All threads should end after maxWait - DBCP-291 > final PoolTest[] pts = new PoolTest[theadCount]; > ThreadGroup threadGroup = new ThreadGroup("testMaxWait"); > > - // Should take ~maxWait for threads to stop > + // Should take ~maxWait for threads to stop > for (int i = 0; i < pts.length; i++) { > (pts[i] = new PoolTest(threadGroup, 1, true)).start(); > } > - > + > // Wait for all the threads to complete > for (int i = 0; i < pts.length; i++) { > final PoolTest poolTest = pts[i]; > poolTest.getThread().join(); > } > > - > + > long end = System.currentTimeMillis(); > - > + > System.out.println("testMaxWait took " + (end-start) + " ms. > Maxwait: "+maxWait); > - > + > // Threads should time out in parallel - allow double that to be safe > - assertTrue((end-start) < (2 * maxWait)); > + assertTrue((end-start) < (2 * maxWait)); > > // Put all the connections back in the pool > for (int i=0; i<c.length; i++) { > @@ -393,25 +394,25 @@ public class TestSharedPoolDataSource ex > public void testTransactionIsolationBehavior() throws Exception { > Connection conn = getConnection(); > assertNotNull(conn); > - assertEquals(Connection.TRANSACTION_READ_COMMITTED, > + assertEquals(Connection.TRANSACTION_READ_COMMITTED, > conn.getTransactionIsolation()); > > conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); > conn.close(); > - > + > Connection conn2 = getConnection(); > - assertEquals(Connection.TRANSACTION_READ_COMMITTED, > + assertEquals(Connection.TRANSACTION_READ_COMMITTED, > conn2.getTransactionIsolation()); > - > + > Connection conn3 = getConnection(); > - assertEquals(Connection.TRANSACTION_READ_COMMITTED, > + assertEquals(Connection.TRANSACTION_READ_COMMITTED, > conn3.getTransactionIsolation()); > conn2.close(); > conn3.close(); > - } > + } > > - // Bugzilla Bug 24136 ClassCastException in DriverAdapterCPDS > + // Bugzilla Bug 24136 ClassCastException in DriverAdapterCPDS > // when setPoolPreparedStatements(true) > - public void testPoolPrepareStatement() throws Exception > + public void testPoolPrepareStatement() throws Exception > { > pcds.setPoolPreparedStatements(true); > > @@ -567,25 +568,66 @@ public class TestSharedPoolDataSource ex > try { > Connection con4 = ds.getConnection("foo", "bay"); // new password > // Idle instances with old password should have been cleared > - assertEquals("Should be no idle connections in the pool", > + assertEquals("Should be no idle connections in the pool", > 0, ((SharedPoolDataSource) ds).getNumIdle()); > con4.close(); > // Should be one idle instance with new pwd > - assertEquals("Should be one idle connection in the pool", > + assertEquals("Should be one idle connection in the pool", > 1, ((SharedPoolDataSource) ds).getNumIdle()); > try { > ds.getConnection("foo", "bar"); // old password > - fail("Should have generated SQLException"); > + fail("Should have generated SQLException"); > } catch (SQLException expected) { > } > Connection con5 = ds.getConnection("foo", "bay"); // take the > idle one > con3.close(); // Return a connection with the old password > ds.getConnection("foo", "bay").close(); // will try bad > returned connection and destroy it > - assertEquals("Should be one idle connection in the pool", > + assertEquals("Should be one idle connection in the pool", > 1, ((SharedPoolDataSource) ds).getNumIdle()); > con5.close(); > } finally { > TesterDriver.addUser("foo","bar"); > } > } > + > + public void testDbcp369() { > + final ArrayList<SharedPoolDataSource> dataSources = > + new ArrayList<SharedPoolDataSource>(); > + for (int j = 0; j < 10000; j++) { > + SharedPoolDataSource dataSource = new SharedPoolDataSource(); > + dataSources.add(dataSource); > + } > + > + Thread t1 = new Thread(new Runnable() { > + @Override > + public void run() { > + for (SharedPoolDataSource dataSource : dataSources) { > + dataSource.setDataSourceName("a"); > + } > + } > + }); > + > + Thread t2 = new Thread(new Runnable() { > + @Override > + public void run() { > + for (SharedPoolDataSource dataSource : dataSources) { > + try { > + dataSource.close(); > + } catch (Exception e) { > + // Ignore > + } > + } > + } > + }); > + > + t1.start(); > + t2.start(); > + > + try { > + t1.join(); > + t2.join(); > + } catch (InterruptedException ie) { > + // Ignore > + } > + } > } > > --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org For additional commands, e-mail: dev-h...@commons.apache.org