Pasi Eronen created DBCP-414:
--------------------------------

             Summary: 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


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)

Reply via email to