race-condition bug causing transactions to fail due to SessionScope to be in an inconsistent state. ---------------------------------------------------------------------------------------------------
Key: IBATIS-286 URL: http://issues.apache.org/jira/browse/IBATIS-286 Project: iBatis for Java Type: Bug Components: SQL Maps Versions: 2.1.7 Environment: JDK 1.4.2 SunOS eqd-dev8.uk.jpmorgan.com 5.8 Generic_117350-28 sun4u sparc SUNW,Sun-Fire-480R Reporter: Scott William Sinclair Summary: Discovered a race-condition bug which causes the com.ibatis.sqlmap.engine.transaction.TransactionManager to throw exceptions due to the com.ibatis.sqlmap.engine.scope.SessionScope being in an inconsistent state. The transaction manager checks the com.ibatis.sqlmap.engine.transaction.TransactionState (STATE_STARTED,STATE_COMMITTED,STATE_ENDED,STATE_USER_PROVIDED) of the SessionScope and throws an exception if it is not in the expected state. Worked Example: a SqlMapSessionImpl s1 is created for a thread t1 and a session scope ss1 is popped from the pool s1.setUserConnection(con) is called to perform a user-controlled transaction the transaction executes normally s1.setUserConnection(null) is called to free up the connection as specified in the iBatis docs The SessionScope ss1 is pushed back into the pool for later use a SqlMapSessionImpl s2 is created for a thread t2 and session scope ss1 is popped from the pool again s2.startTransaction(...) is called to start a conventional iBatis managed transaction on thread t2 with SessionScope ss1 s1.startTransaction(...) is called to start a conventional iBatis managed transaction on thread t1 with SessionScope ss1 thread t1 throws the exception <INSERT HERE> this is because SessionScope ss1 has been initialized by s2.startTransaction() causing the s1.startTransaction() to fail due to the unexpected state of ss1. SqlMapSessionImpl s1 is closed and SessionScope ss1 is returned to the pool for later use. s2.execute(...) is called to execute statements s2.commitTransaction(...) is called to commit the iBatis managed transaction s2.endTransaction(...) is called to complete the iBatis managed transaction this closes SqlMapSessionImpl s2, and SessionScope ss1 is returned to the pool for later use. SessionScope ss1 is now in the pool twice, causing ss1 to be available to multiple threads. This results in the same race condition reoccurring. Fix: Ensure that SqlMapSessionImpl is closed when SqlMapSessionImpl.setUserConnection(null) is called. Class com.ibatis.sqlmap.engine.impl.SqlMapClientImpl: public void setUserConnection(Connection connection) throws SQLException { try { getLocalSqlMapSession().setUserConnection(connection); } finally { if (connection == null) { //bug fix:if connection == null, close the session like we are ending a transaction getLocalSqlMapSession().close(); } } } Remove the push(SessionScope) call in SqlMapExecutorDelegate.setUserProvidedTransaction(SessionScope session, Connection userConnection) when userConnection is null. This makes sure that the SessionScope is not pushed twice. Class com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate: public void setUserProvidedTransaction(SessionScope session, Connection userConnection) { if (session.getTransactionState() == TransactionState.STATE_USER_PROVIDED) { session.recallTransactionState(); } if (userConnection != null) { Connection conn = userConnection; session.saveTransactionState(); session.setTransaction(new UserProvidedTransaction(conn)); session.setTransactionState(TransactionState.STATE_USER_PROVIDED); } else { session.setTransaction(null); //pushSession(session); bug fix: commented out done as part of closing the SqlMapSessionImpl } } -- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://issues.apache.org/jira/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira