atomikos / transactions-essentials

Development repository for next major release of
https://www.atomikos.com/Main/TransactionsEssentials
Other
462 stars 139 forks source link

Prepare transaction during rollback #201

Open alexo134 opened 8 months ago

alexo134 commented 8 months ago

Our application, based on Spring Boot 3.1.6 with latest Transaction Essentials 6.0.0, is connected to PostgreSQL 13.x. Provided to us, Postgres instance has disabled prepared transactions (max_prepared_transactions set t 0). Everything runs ok until a transaction rollback caused by transaction timeout, for example. Atomikos then tries to execute "prepare transaction" statement on the connection even though we have only single resource (database connection) in the JTA transaction. It's causing an exception in the driver as the prepared transactions are disabled:

2023-12-01 12:01:46.314 ERROR [6569bcc933d1962e4739abd7b1abb390] 1 --- [nerThread-52867] c.a.datasource.xa.XAResourceTransaction  : XA resource 'dataSource': prepare for XID 'XID: 3139322E3136382E3133302E3130312E746D313730313432383434323635303130303235:3139322E3136382E3133302E3130312E746D313035363735' raised -7: the XA resource has become unavailable org.postgresql.xa.PGXAException: Error preparing transaction. prepare xid=XID: 3139322E3136382E3133302E3130312E746D313730313432383434323635303130303235:3139322E3136382E3133302E3130312E746D313035363735
    at org.postgresql.xa.PGXAConnection.prepare(PGXAConnection.java:365) ~[postgresql-42.6.0.jar:42.6.0]
    at com.atomikos.datasource.xa.XAResourceTransaction.prepare(XAResourceTransaction.java:304) ~[transactions-jta-6.0.0-jakarta.jar:na]
    at com.atomikos.icatch.imp.PrepareMessage.send(PrepareMessage.java:40) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.PrepareMessage.send(PrepareMessage.java:19) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.PropagationMessage.submit(PropagationMessage.java:67) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.Propagator$PropagatorThread.run(Propagator.java:63) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.Propagator.submitPropagationMessage(Propagator.java:42) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.ActiveStateHandler.prepare(ActiveStateHandler.java:172) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.CoordinatorImp.prepare(CoordinatorImp.java:522) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.CoordinatorImp.terminate(CoordinatorImp.java:681) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.CompositeTransactionImp.commit(CompositeTransactionImp.java:279) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.jta.TransactionImp.commit(TransactionImp.java:178) ~[transactions-jta-6.0.0-jakarta.jar:na]
    at com.atomikos.icatch.jta.TransactionManagerImp.commit(TransactionManagerImp.java:428) ~[transactions-jta-6.0.0-jakarta.jar:na]
    at com.atomikos.icatch.jta.UserTransactionManager.commit(UserTransactionManager.java:160) ~[transactions-jta-6.0.0-jakarta.jar:na]
    at org.springframework.transaction.jta.JtaTransactionManager.doCommit(JtaTransactionManager.java:1026) ~[spring-tx-6.0.11.jar:6.0.11]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743) ~[spring-tx-6.0.11.jar:6.0.11]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-6.0.11.jar:6.0.11]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:660) ~[spring-tx-6.0.11.jar:6.0.11
 ...
Caused by: org.postgresql.util.PSQLException: ERROR: prepared transactions are disabled
  Hint: Set max_prepared_transactions to a nonzero value.
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2713) ~[postgresql-42.6.0.jar:42.6.0]
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2401) ~[postgresql-42.6.0.jar:42.6.0]
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:368) ~[postgresql-42.6.0.jar:42.6.0]

After investigation, we have found the problematic code in the CoordinatorImp class:

    protected void terminate ( boolean commit ) throws HeurRollbackException,
            HeurMixedException, SysException, java.lang.SecurityException,
            HeurCommitException, HeurHazardException, RollbackException,
            IllegalStateException

    {    
        synchronized ( fsm_ ) {
            if ( commit ) {
                if ( participants_.size () <= 1 ) {
                    commit ( true );
                } else {
                    int prepareResult = prepare ();
                    // make sure to only do commit if NOT read only
                    if ( prepareResult != Participant.READ_ONLY )
                        commit ( false );
                }
            } else {
                rollback ();
            }
        }
    }

Participants count should be maximum one as we have only one database. After debugging, we have found that an additional "fake" participant RollbackOnlyParticipant was added to the list to make rollback happen. Therefore, the coordinator is treating this transaction as it had multiple resources.

In my opinion, rollback participant should not be considered as a normal resource.

Additionally, I've created a setup with Non-XA data source (and local-transaction-mode set to true) to avoid calling prepare statement. But still a simple rollback is causing strange warning and exception:

From AtomikosNonXAParticipant.prepare():

c.a.j.internal.AtomikosNonXAParticipant  : com.atomikos.jdbc.AtomikosNonXADataSourceBean 'nonXaDataSource' [NB: this resource does not support two-phase commit unless configured as readOnly]

and error:

com.atomikos.icatch.RollbackException: Prepare failed because one or more resources refused to commit. This transaction has been rolled back instead. The cause could be either:
1. a transaction timeout (in which case you should see additional timeout warnings in this log file), or
2. inability to reach the resource (in which case you should see network errors), or
3. a resource-internal cause that we can’t inspect
    at com.atomikos.icatch.imp.ActiveStateHandler.prepare(ActiveStateHandler.java:207) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.CoordinatorImp.prepare(CoordinatorImp.java:522) ~[transactions-6.0.0.jar:na]
    at com.atomikos.icatch.imp.CoordinatorImp.terminate(CoordinatorImp.java:681) ~[transactions-6.0.0.jar:na]

Expecting here just a simple rollback of a timed out transaction.