Skip to content

Reset the rollbackOnly marker when a managed connection is recycled#2253

Open
BassemH wants to merge 1 commit into
apache:mainfrom
BassemH:ra-reset-rollbackonly-on-managed-connection-cleanup
Open

Reset the rollbackOnly marker when a managed connection is recycled#2253
BassemH wants to merge 1 commit into
apache:mainfrom
BassemH:ra-reset-rollbackonly-on-managed-connection-cleanup

Conversation

@BassemH

@BassemH BassemH commented Jul 25, 2026

Copy link
Copy Markdown

Problem

LocalAndXATransaction.end(xid, TMFAIL) marks the TransactionContext rollbackOnly so
that no further work is done in the transaction that has just failed, and
ActiveMQSession.send() refuses to send while that marker is set (AMQ-7485). The marker
is cleared only by TransactionContext.begin() and start(xid, flags), that is, at the
next transaction boundary on that context, which matches the stated intent of AMQ-7485:
"failed ended transactions prevent further work till next transaction boundary".

In the resource adapter, though, the TransactionContext is not transaction scoped.
ActiveMQManagedConnection holds it in a final field created from the physical
connection, so it lives as long as the pooled connection, and every session handed out
from that managed connection observes it because
ManagedTransactionContext.isRollbackOnly() consults the shared context unconditionally:

public boolean isRollbackOnly() {
    return sharedContext.isRollbackOnly() || super.isRollbackOnly();
}

So when a container returns a managed connection to the pool after a rolled back
transaction and later hands it out for work that is not part of a transaction, there
is no next transaction boundary. Nothing clears the marker, and every send on that pooled
connection fails with:

jakarta.jms.IllegalStateException: transaction marked rollback only
    at org.apache.activemq.ActiveMQSession.send(ActiveMQSession.java:1975)
    at org.apache.activemq.ActiveMQMessageProducer.send(ActiveMQMessageProducer.java:329)

The connection stays unusable for non transacted sends until it happens to be enlisted in
a transaction again. ManagedConnection.cleanup() does not reset the marker, and because
ActiveMQManagedConnectionFactory does not implement ValidatingManagedConnectionFactory
the container cannot detect or evict the affected connection either.

This is easy to hit on a JCA container. IronJacamar's TxConnectionListener.delist()
delists with TMFAIL whenever the transaction status is STATUS_MARKED_ROLLBACK, and
Narayana's XAResourceRecord.topLevelAbort() ends the branch with TMFAIL, so any
rolled back transaction that touched the RA leaves that pooled connection unusable for
subsequent non transactional sends. We hit this on JBoss EAP 8 / WildFly after upgrading
the client from 5.9.1 to 6.2.4; every @TransactionAttribute(NOT_SUPPORTED) send in the
application started failing intermittently, and the reported error mentions a transaction
even though no transaction is involved at the point of failure.

Fix

Clear the marker in LocalAndXATransaction.cleanup().

That method is only reached from ManagedConnection.cleanup(), which is the point where
the container recycles the physical connection, and it already discards the rest of the
transaction scoped state by delegating to TransactionContext.cleanup() (which nulls
associatedXid and transactionId, but not rollbackOnly).

end(TMFAIL) sets the marker after setInManagedTx(false), so a session that took
part in the failed transaction still sees it. The behaviour AMQ-7485 added, and the
testSendOnAbortedXATx test that asserts it, are unchanged.

Test

JmsXAQueueTransactionTest#testSendOnRecycledManagedConnectionAfterAbortedXATx aborts an
XA transaction with TMFAIL, recycles the managed connection via
ManagedConnection.cleanup(), then obtains a fresh handle and sends outside of any
transaction.

Without the change it fails:

junit.framework.AssertionFailedError: recycled managed connection must not stay rollbackOnly
    at org.apache.activemq.ra.JmsXAQueueTransactionTest
       .testSendOnRecycledManagedConnectionAfterAbortedXATx(JmsXAQueueTransactionTest.java:205)

With the change the whole activemq-ra module passes:

Tests run: 119, Failures: 0, Errors: 0, Skipped: 2

which includes testSendOnAbortedXATx, JmsXARollback2CxTransactionTest,
FailoverManagedConnectionTest and FailoverManagedClusterTest (the failover with
pending acks scenario that AMQ-5854 originally introduced the marker for).

The change also cherry-picks cleanly onto activemq-6.2.x.

I was not able to open a JIRA issue for this (ASF JIRA no longer allows self service
account creation), so the commit carries no issue key. Happy to add one if a committer
creates the issue.

LocalAndXATransaction.end(xid, TMFAIL) marks the TransactionContext
rollbackOnly so that no further work is done in the transaction that has
just failed (AMQ-7485). The marker is only ever cleared by
TransactionContext.begin() or start(xid, flags), that is, at the next
transaction boundary on that context.

In the resource adapter the TransactionContext is owned by
ActiveMQManagedConnection, so it lives as long as the pooled physical
connection rather than as long as the transaction. When the application
server returns such a connection to the pool after a rolled back
transaction and later hands it out again for work that is not part of a
transaction, there is no new transaction boundary, the marker is still
set, and ActiveMQSession.send() rejects every send with "transaction
marked rollback only". The connection stays unusable for non transacted
sends until it happens to be enlisted in a transaction again.

Clear the marker in LocalAndXATransaction.cleanup(). That method is only
reached from ManagedConnection.cleanup(), which is the point at which the
container recycles the physical connection, and it already discards the
other transaction scoped state via TransactionContext.cleanup().
end(TMFAIL) still leaves the marker set for the session that took part in
the failed transaction, so the behaviour asserted by AMQ-7485 is
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant