[ https://issues.apache.org/jira/browse/DBCP-414?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Phil Steitz updated DBCP-414: ----------------------------- Fix Version/s: 2.0.1 > PoolablePreparedStatement can get closed while it's being used > -------------------------------------------------------------- > > Key: DBCP-414 > URL: https://issues.apache.org/jira/browse/DBCP-414 > Project: Commons Dbcp > Issue Type: Bug > Affects Versions: 2.0 > Environment: DBCP 2.0 > Reporter: Pasi Eronen > Fix For: 2.0.1 > > > I've found a case where a PoolablePreparedStatement that's currently in > active use can get closed (resulting in "PoolablePreparedStatement with > address: ... is closed" SQLException to the application), from a finalizer of > another DelegatingPreparedStatement (that's obviously not in use, since it's > being garbage collected). > Details: > 0. Starting point: application has a Connection (it's a > PoolGuardConnectionWrapper wrapping PoolableConnection wrapping > PoolingConnection wrapping com.mysql.jdbc.JDBC4Connection), statement pooling > is enabled, and prepareStatement has never been called > 1. Application calls conn.prepareStatement. The statement pool is empty, so > this ends up in PoolingConnection.makeObject, which then calls MySQL to get a > PreparedStatement (stmt1), and wraps it in PoolablePreparedStatement (stmt2). > PoolableConnection.prepareStatement wraps it in new > DelegatingPreparedStatement (stmt3), and PoolGuardConnectionWrapper wraps it > again (stmt4). > 2. Application does something with stmt4, and eventually calls stmt4.close(). > The call ends up in stmt2.close (in PoolablePreparedStatement) which returns > stmt2 to the statement pool. Stmt2.passivate() gets called, setting the > _closed flag to true. > 3. Application forgets all about stmt4 > 4. Later, application calls conn.prepareStatement (with same SQL) again. The > result is the old MySQL PreparedStatement (stmt1) wrapped in the old > PoolablePreparedStatement (stmt2) wrapped in new DelegatingPreparedStatement > stmt5 wrapped in new DelegatingPreparedStatement stmt6. Stmt2.activate() > gets called, setting the _closed flag to false. > 5. Now, garbage collection gets run, and stmt4 gets gc'd. stmt4.finalize() > calls stmt4.close() which calls stmt3.close() which calls stmt2.close(). > Since stmt2 is not closed any more (_closed was just set to false above), it > gets closed and returned to the pool. > 6. Applications calls something in stmt6, which ends up in stmt2, and > stmt2.checkOpen() throws exception java.sql.SQLException: > org.apache.commons.dbcp2.PoolablePreparedStatement with address: > "com.mysql.jdbc.ServerPreparedStatement[12] - select 1" is closed. > PoolGuardConnectionWrapper prevents the application from accidentally calling > stmt4.close() later again, but this doesn't prevent the finalizer.... DBCP > 1.4 did not have any finalizers, so it doesn't have this bug. > I guess DelegatingStatement.close() should check isClosed(), and in the > finally block, also do setDelegate(null) (like PoolGuardConnectionWrapper > does), to make sure the delegate (which might be in active use by someone > else) can never get called via this DelegatingStatement again (although most > methods in DelegatingStatement start with checkOpen(), not all of them do!). -- This message was sent by Atlassian JIRA (v6.2#6252)