You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ps...@apache.org on 2007/11/25 21:02:09 UTC

svn commit: r598045 - in /commons/proper/dbcp/trunk: ./ src/java/org/apache/commons/dbcp/datasources/ src/test/org/apache/commons/dbcp/datasources/ xdocs/

Author: psteitz
Date: Sun Nov 25 12:02:07 2007
New Revision: 598045

URL: http://svn.apache.org/viewvc?rev=598045&view=rev
Log:
Improved error recovery and listener cleanup in KeyedCPDSConnectionFactory. 
* Substituted calls to destroyObject with _pool.invalidateObject on error to
  ensure pool active count is decremented on error events.
* Ensured that events from closed or invalid connections are ignored and
  listeners are cleaned up. Generalized validatingMap to "muteMap" (map of
  PoolableConnections from which events are ignored) and postponed cleanup
  of this and pcMap to take place in makeObject.
* Removed calls to removeConnectionEventListener from connection event handlers
  to eliminate potential for ConcurrentModificationExceptions. Added a cleanupMap
  to keep track of PoolablConnections that need to have listeners cleaned up. 

JIRA: DBCP-216
Reported (and patched) by Marcos Sanz

Added:
    commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/ConnectionPoolDataSourceProxy.java   (with props)
    commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/PooledConnectionProxy.java   (with props)
    commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/TestKeyedCPDSConnectionFactory.java   (with props)
Modified:
    commons/proper/dbcp/trunk/pom.xml
    commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/datasources/KeyedCPDSConnectionFactory.java
    commons/proper/dbcp/trunk/xdocs/changes.xml

Modified: commons/proper/dbcp/trunk/pom.xml
URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/pom.xml?rev=598045&r1=598044&r2=598045&view=diff
==============================================================================
--- commons/proper/dbcp/trunk/pom.xml (original)
+++ commons/proper/dbcp/trunk/pom.xml Sun Nov 25 12:02:07 2007
@@ -252,6 +252,7 @@
                 <include>org/apache/commons/dbcp/TestJndi.java</include>
                 
                 <include>org/apache/commons/dbcp/datasources/TestFactory.java</include>
+                <include>org/apache/commons/dbcp/datasources/TestKeyedCPDSConnectionFactory.java</include>
                 <include>org/apache/commons/dbcp/datasources/TestPerUserPoolDataSource.java</include>
                 <include>org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java</include>
 

Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/datasources/KeyedCPDSConnectionFactory.java
URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/datasources/KeyedCPDSConnectionFactory.java?rev=598045&r1=598044&r2=598045&view=diff
==============================================================================
--- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/datasources/KeyedCPDSConnectionFactory.java (original)
+++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/datasources/KeyedCPDSConnectionFactory.java Sun Nov 25 12:02:07 2007
@@ -22,6 +22,7 @@
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.WeakHashMap;
 
@@ -52,7 +53,24 @@
     protected String _validationQuery = null;
     protected boolean _rollbackAfterValidation = false;
     protected KeyedObjectPool _pool = null;
-    private Map validatingMap = new HashMap();
+    
+    /** 
+     * Map of "muted" PooledConnections for which close events
+     * are ignored. Connections are muted when they are being validated
+     * or when they have been marked for removal.
+     */
+    private Map muteMap = new HashMap();
+    
+    /**
+     * Map of PooledConnections marked for removal. Connections are
+     * marked for removal from pcMap and muteMap in destroyObject.
+     * Cleanup happens in makeObject.
+     */
+    private Map cleanupMap = new HashMap();
+    
+    /**
+     * Map of PooledConnectionAndInfo instances
+     */
     private WeakHashMap pcMap = new WeakHashMap();
 
     /**
@@ -169,15 +187,22 @@
         pc.addConnectionEventListener(this);
         obj = new PooledConnectionAndInfo(pc, username, password);
         pcMap.put(pc, obj);
+        cleanupListeners();
 
         return obj;
     }
 
+    /**
+     * Closes the PooledConnection and marks it for removal from the
+     * pcMap and for listener cleanup. Adds it to muteMap so connectionClosed
+     * events generated by this or other actions are ignored.
+     */
     public void destroyObject(Object key, Object obj) throws Exception {
         if (obj instanceof PooledConnectionAndInfo) {
             PooledConnection pc = ((PooledConnectionAndInfo)obj).getPooledConnection();
-            pcMap.remove(pc);
-            pc.close();
+            cleanupMap.put(pc, null); // mark for cleanup
+            muteMap.put(pc, null);    // ignore events until listener is removed
+            pc.close(); 
         }
     }
 
@@ -195,7 +220,7 @@
                 // before another one can be requested and closing it will
                 // generate an event. Keep track so we know not to return
                 // the PooledConnection
-                validatingMap.put(pconn, null);
+                muteMap.put(pconn, null);
                 try {
                     conn = pconn.getConnection();
                     stmt = conn.createStatement();
@@ -232,7 +257,7 @@
                             // ignore
                         }
                     }
-                    validatingMap.remove(pconn);
+                    muteMap.remove(pconn);
                 }
             } else {
                 valid = true;
@@ -261,9 +286,10 @@
      */
     public void connectionClosed(ConnectionEvent event) {
         PooledConnection pc = (PooledConnection)event.getSource();
-        // if this event occured becase we were validating, ignore it
+        // if this event occurred because we were validating, or if this
+        // connection has been marked for removal, ignore it
         // otherwise return the connection to the pool.
-        if (!validatingMap.containsKey(pc)) {
+        if (!muteMap.containsKey(pc)) {
             PooledConnectionAndInfo info =
                 (PooledConnectionAndInfo) pcMap.get(pc);
             if (info == null) {
@@ -273,13 +299,15 @@
                 _pool.returnObject(info.getUserPassKey(), info);
             } catch (Exception e) {
                 System.err.println("CLOSING DOWN CONNECTION AS IT COULD " +
-                                   "NOT BE RETURNED TO THE POOL");
+                "NOT BE RETURNED TO THE POOL");
+                cleanupMap.put(pc, null); // mark for cleanup
+                muteMap.put(pc, null);    // ignore events until listener is removed
                 try {
-                    destroyObject(info.getUserPassKey(), info);
-                } catch (Exception e2) {
+                    _pool.invalidateObject(info.getUserPassKey(), info);
+                } catch (Exception e3) {
                     System.err.println("EXCEPTION WHILE DESTROYING OBJECT " +
-                                       info);
-                    e2.printStackTrace();
+                            info);
+                    e3.printStackTrace();
                 }
             }
         }
@@ -291,15 +319,17 @@
      */
     public void connectionErrorOccurred(ConnectionEvent event) {
         PooledConnection pc = (PooledConnection)event.getSource();
+        if (cleanupMap.containsKey(pc)) {
+            return; // waiting for listener cleanup - ignore event
+        }
         try {
             if (null != event.getSQLException()) {
                 System.err
                     .println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" +
                              event.getSQLException() + ")");
             }
-            //remove this from the listener list because we are no more
-            //interested in errors since we are about to close this connection
-            pc.removeConnectionEventListener(this);
+            cleanupMap.put(pc, null); // mark for cleanup
+            muteMap.put(pc, null);    // ignore events until listener is removed
         } catch (Exception ignore) {
             // ignore
         }
@@ -309,10 +339,37 @@
             throw new IllegalStateException(NO_KEY_MESSAGE);
         }
         try {
-            destroyObject(info.getUserPassKey(), info);
+            _pool.invalidateObject(info.getUserPassKey(), info);
         } catch (Exception e) {
             System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
             e.printStackTrace();
+        }
+    }
+    
+    /**
+     * Cleans up pooled connections from cleanupMap.
+     * 1) remove PoolableConnectionAndInfo wrapper from pcMap
+     * 2) remove from MuteMap
+     */
+    private void cleanupListeners() { 
+        try {
+            Iterator iter = cleanupMap.keySet().iterator();
+            while (iter.hasNext()) {
+                PooledConnection pc = (PooledConnection) iter.next();
+                pcMap.remove(pc);
+                muteMap.remove(pc);
+                try {
+                    pc.removeConnectionEventListener(this);
+                } catch (Exception ex) {
+                    System.err.println("EXCEPTION REMOVING CONNECTION LISTENER ");
+                    ex.printStackTrace(); 
+                }
+            }
+        } catch (Exception e) {
+            System.err.println("EXCEPTION CLEANING UP CONNECTION LISTENERS ");
+            e.printStackTrace();  
+        } finally {
+            cleanupMap.clear();
         }
     }
 }

Added: commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/ConnectionPoolDataSourceProxy.java
URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/ConnectionPoolDataSourceProxy.java?rev=598045&view=auto
==============================================================================
--- commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/ConnectionPoolDataSourceProxy.java (added)
+++ commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/ConnectionPoolDataSourceProxy.java Sun Nov 25 12:02:07 2007
@@ -0,0 +1,83 @@
+/*
+ * 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.dbcp.datasources;
+
+import java.io.PrintWriter;
+import java.sql.SQLException;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.PooledConnection;
+
+/**
+ * ConnectionPoolDataSource implementation that proxies another 
+ * ConnectionPoolDataSource.
+ * 
+ * @version $Revision$ $Date$
+ */
+public class ConnectionPoolDataSourceProxy implements ConnectionPoolDataSource {
+
+    protected ConnectionPoolDataSource delegate = null;
+    
+    public ConnectionPoolDataSourceProxy(ConnectionPoolDataSource cpds) {
+        this.delegate = cpds;
+    }
+    
+    public ConnectionPoolDataSource getDelegate() {
+        return delegate;
+    }
+    
+    public int getLoginTimeout() throws SQLException {
+        return delegate.getLoginTimeout();
+    }
+   
+    public PrintWriter getLogWriter() throws SQLException {
+        return delegate.getLogWriter();
+    }
+
+    /**
+     * Return a TesterPooledConnection with notifyOnClose turned on
+     */
+    public PooledConnection getPooledConnection() throws SQLException {
+        return wrapPooledConnection(delegate.getPooledConnection());
+    }
+
+    /**
+     * Return a TesterPooledConnection with notifyOnClose turned on
+     */
+    public PooledConnection getPooledConnection(String user, String password)
+            throws SQLException {
+        return wrapPooledConnection(delegate.getPooledConnection(user, password));
+    }
+
+    public void setLoginTimeout(int seconds) throws SQLException {
+        delegate.setLoginTimeout(seconds);     
+    }
+
+    public void setLogWriter(PrintWriter out) throws SQLException {
+        delegate.setLogWriter(out);     
+    }
+    
+    /**
+     * Create a TesterPooledConnection with notifyOnClose turned on
+     */
+    protected PooledConnection wrapPooledConnection(PooledConnection pc) {
+        PooledConnectionProxy tpc = new PooledConnectionProxy(pc);
+        tpc.setNotifyOnClose(true);
+        return tpc; 
+    }
+
+}

Propchange: commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/ConnectionPoolDataSourceProxy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/PooledConnectionProxy.java
URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/PooledConnectionProxy.java?rev=598045&view=auto
==============================================================================
--- commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/PooledConnectionProxy.java (added)
+++ commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/PooledConnectionProxy.java Sun Nov 25 12:02:07 2007
@@ -0,0 +1,139 @@
+/*
+ * 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.dbcp.datasources;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.sql.ConnectionEvent;
+import javax.sql.ConnectionEventListener;
+import javax.sql.PooledConnection;
+
+/**
+ * PooledConnection implementation that wraps a driver-supplied
+ * PooledConnection and proxies events, allowing behavior to be
+ * modified to simulate behavior of different implementations.
+ * 
+ * @version $Revision$ $Date$
+ */
+public class PooledConnectionProxy implements PooledConnection,
+    ConnectionEventListener {
+
+    protected PooledConnection delegate = null;
+    
+    /**
+     * ConnectionEventListeners
+     */
+    private Vector eventListeners = new Vector();
+    
+    /** 
+     * True means we will (dubiously) notify listeners with a
+     * ConnectionClosed event when this (i.e. the PooledConnection itself)
+     * is closed
+     */
+    private boolean notifyOnClose = false;
+    
+    public PooledConnectionProxy(PooledConnection pooledConnection) {
+        this.delegate = pooledConnection;
+        pooledConnection.addConnectionEventListener(this);
+    }
+
+    /** 
+     * If notifyOnClose is on, notify listeners
+     */
+    public void close() throws SQLException {
+        delegate.close();
+        if (isNotifyOnClose()) {
+           notifyListeners();
+        }
+    }
+
+    public Connection getConnection() throws SQLException {
+        return delegate.getConnection();
+    }
+
+    public void removeConnectionEventListener(ConnectionEventListener listener) {
+        eventListeners.remove(listener);
+    }
+
+    public boolean isNotifyOnClose() {
+        return notifyOnClose;
+    }
+
+    public void setNotifyOnClose(boolean notifyOnClose) {
+        this.notifyOnClose = notifyOnClose;
+    }
+    
+    /**
+     * sends a connectionClosed event to listeners.
+     */
+    void notifyListeners() {
+        ConnectionEvent event = new ConnectionEvent(this);
+        Iterator i = eventListeners.iterator();
+        while (i.hasNext()) {
+            ((ConnectionEventListener) i.next()).connectionClosed(event);
+        }
+    }
+    
+    /**
+     * Add an event listener.
+     */
+    public void addConnectionEventListener(ConnectionEventListener listener) {
+        if (!eventListeners.contains(listener)) {
+            eventListeners.add(listener);
+        }
+    }
+
+    
+    /**
+     * Pass closed events on to listeners
+     */
+    public void connectionClosed(ConnectionEvent event) {
+        notifyListeners();    
+    }
+
+    /**
+     * Pass error events on to listeners
+     */ 
+    public void connectionErrorOccurred(ConnectionEvent event) {
+        Iterator i = eventListeners.iterator();
+        while (i.hasNext()) {
+            ((ConnectionEventListener) i.next()).connectionErrorOccurred(event);
+        } 
+    }
+    
+    /**
+     * Generate a connection error event
+     */
+    public void throwConnectionError() {
+        ConnectionEvent event = new ConnectionEvent(this);
+        connectionErrorOccurred(event);
+    }
+    
+    /**
+     * Expose listeners
+     */
+    public Collection getListeners() {
+        return eventListeners;
+    }
+
+}

Propchange: commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/PooledConnectionProxy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/TestKeyedCPDSConnectionFactory.java
URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/TestKeyedCPDSConnectionFactory.java?rev=598045&view=auto
==============================================================================
--- commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/TestKeyedCPDSConnectionFactory.java (added)
+++ commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/TestKeyedCPDSConnectionFactory.java Sun Nov 25 12:02:07 2007
@@ -0,0 +1,156 @@
+/*
+ * 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.dbcp.datasources;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.sql.PooledConnection;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS;
+
+import org.apache.commons.pool.impl.GenericKeyedObjectPool;
+
+/**
+ * @version $Revision$ $Date$
+ */
+public class TestKeyedCPDSConnectionFactory extends TestCase {
+   
+    protected ConnectionPoolDataSourceProxy cpds = null;
+    
+    public TestKeyedCPDSConnectionFactory(String testName) {
+        super(testName);
+    }
+
+    public static Test suite() {
+        return new TestSuite(TestKeyedCPDSConnectionFactory.class);
+    }
+
+    public void setUp() throws Exception {
+        cpds = new ConnectionPoolDataSourceProxy(new DriverAdapterCPDS());
+        DriverAdapterCPDS delegate = (DriverAdapterCPDS) cpds.getDelegate();
+        delegate.setDriver("org.apache.commons.dbcp.TesterDriver");
+        delegate.setUrl("jdbc:apache:commons:testdriver");
+        delegate.setUser("username");
+        delegate.setPassword("password");
+    }
+    
+    /**
+     * JIRA DBCP-216
+     * 
+     * Check PoolableConnection close triggered by destroy is handled
+     * properly. PooledConnectionProxy (dubiously) fires connectionClosed
+     * when PooledConnection itself is closed.
+     */
+    public void testSharedPoolDSDestroyOnReturn() throws Exception {
+       SharedPoolDataSource ds = new SharedPoolDataSource();
+       ds.setConnectionPoolDataSource(cpds);
+       ds.setMaxActive(10);
+       ds.setMaxWait(50);
+       ds.setMaxIdle(2);
+       Connection conn1 = ds.getConnection("username", "password");
+       Connection conn2 = ds.getConnection("username", "password");
+       Connection conn3 = ds.getConnection("username", "password");
+       assertEquals(3, ds.getNumActive());
+       conn1.close();
+       assertEquals(1, ds.getNumIdle());
+       conn2.close();
+       assertEquals(2, ds.getNumIdle());
+       conn3.close(); // Return to pool will trigger destroy -> close sequence
+       assertEquals(2, ds.getNumIdle());
+    }
+    
+    /**
+     * JIRA DBCP-216
+     * 
+     * Verify that pool counters are maintained properly and listeners are
+     * cleaned up when a PooledConnection throws a connectionError event.
+     */
+    public void testConnectionErrorCleanup() throws Exception {
+        // Setup factory
+        UserPassKey key = new UserPassKey("username", "password");
+        GenericKeyedObjectPool pool = new GenericKeyedObjectPool(null);
+        KeyedCPDSConnectionFactory factory = 
+            new KeyedCPDSConnectionFactory(cpds, pool, null, false);
+        
+        // Checkout a pair of connections
+        PooledConnection pcon1 = 
+            ((PooledConnectionAndInfo) pool.borrowObject(key))
+                .getPooledConnection();
+        Connection con1 = pcon1.getConnection();
+        PooledConnection pcon2 = 
+            ((PooledConnectionAndInfo) pool.borrowObject(key))
+                .getPooledConnection();
+        assertEquals(2, pool.getNumActive(key));
+        assertEquals(0, pool.getNumIdle(key));
+        
+        // Verify listening
+        PooledConnectionProxy pc = (PooledConnectionProxy) pcon1;
+        assertTrue(pc.getListeners().contains(factory));
+        
+        // Throw connectionError event
+        pc.throwConnectionError();
+        
+        // Active count should be reduced by 1 and no idle increase
+        assertEquals(1, pool.getNumActive(key));
+        assertEquals(0, pool.getNumIdle(key));
+        
+        // Throw another one - we should be on cleanup list, so ignored
+        pc.throwConnectionError();
+        assertEquals(1, pool.getNumActive(key));
+        assertEquals(0, pool.getNumIdle(key));
+        
+        // Ask for another connection - should trigger makeObject, which causes
+        // cleanup, removing listeners.
+        PooledConnection pcon3 = 
+            ((PooledConnectionAndInfo) pool.borrowObject(key))
+                .getPooledConnection();
+        assertTrue(!pcon3.equals(pcon1)); // better not get baddie back
+        assertTrue(!pc.getListeners().contains(factory)); // verify cleanup
+        assertEquals(2, pool.getNumActive(key));
+        assertEquals(0, pool.getNumIdle(key));
+        
+        // Return good connections back to pool
+        pcon2.getConnection().close();
+        pcon3.getConnection().close();
+        assertEquals(2, pool.getNumIdle(key));
+        assertEquals(0, pool.getNumActive(key));
+        
+        // Verify pc is closed
+        try {
+           pc.getConnection();
+           fail("Expecting SQLException using closed PooledConnection");
+        } catch (SQLException ex) {
+            // expected
+        }
+        
+        // Back from the dead - ignore the ghost!
+        con1.close();
+        assertEquals(2, pool.getNumIdle(key));
+        assertEquals(0, pool.getNumActive(key));
+        
+        // Clear pool
+        factory.getPool().clear();
+        assertEquals(0, pool.getNumIdle(key));
+    }
+
+}

Propchange: commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/datasources/TestKeyedCPDSConnectionFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/dbcp/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/xdocs/changes.xml?rev=598045&r1=598044&r2=598045&view=diff
==============================================================================
--- commons/proper/dbcp/trunk/xdocs/changes.xml (original)
+++ commons/proper/dbcp/trunk/xdocs/changes.xml Sun Nov 25 12:02:07 2007
@@ -71,7 +71,7 @@
       </action>
       <action dev="psteitz" type="fix" issue="DBCP-11" due-to="Dain Sundstrom">
         Modified PoolingDataSource, PoolingDriver and DelegatingStatement to
-        assure that all all returned Statements, PreparedStatements, 
+        assure that all returned Statements, PreparedStatements, 
         CallableStatements and ResultSets are wrapped with a delegating object,
         which already properly handle the back pointers for Connection and
         Statement.  Also added tests to to assure that the *same* object used 
@@ -91,6 +91,15 @@
       <action dev="psteitz" type="fix" issue="DBCP-241">
         Eliminated potential sources of NullPointerExceptions in 
         PoolingConnection.
+      </action>
+      <action dev="psteitz" type="fix" issue="DBCP-216" due-to="Marcos Sanz">
+        Improved error recovery and listener cleanup in 
+        KeyedCPDSConnectionFactory. Substituted calls to destroyObject with
+         _pool.invalidateObject on error to ensure pool active count is
+        decremented on error events. Ensured that events from closed or invalid
+        connections are ignored and listeners are cleaned up. Removed calls to
+        removeConnectionEventListener from connection event handlers to
+        eliminate potential for ConcurrentModificationExceptions.
       </action>
     </release>
     <release version="1.2.2" date="2007-04-04"