TOMEE-4652 - roll back UserTransaction left over by a request - #2849
TOMEE-4652 - roll back UserTransaction left over by a request#2849jungm wants to merge 1 commit into
Conversation
A servlet or JSP that leaves a bean managed UserTransaction incomplete leaks that transaction to the next request served on the same pooled Tomcat exec thread. Geronimo's TransactionManagerImpl keeps the thread-to-transaction association (and the per-thread transaction timeout) in ThreadLocals that are only cleared on commit()/rollback(). EJBs are wrapped by container interceptors that restore the thread state; plain servlets have no equivalent, and OpenEJBValve's finally block cleaned up only the security context. Add TransactionCleanup, invoked from the request teardown finally in OpenEJBValve (sync path) and OpenEJBSecurityListener.asyncExit() (async complete/error/timeout). It rolls back and unassociates any dangling transaction and resets the per-thread transaction timeout, which leaks the same way. Also make CoreUserTransaction.resetError(null) remove() the ThreadLocal instead of set(null) so pooled threads don't keep an empty entry pinned. Adds UserTransactionLeakTest, which forces two sequential requests onto one exec thread (maxThreads=1) and asserts the second sees no leaked transaction.
|
Verified the premise against the Geronimo 4.0.0 sources — The placement rationale is wrong, though, and it should be corrected in the javadoc
Smaller:
Merge precondition rather than a code comment: please re-run the Jakarta Transactions TCK |
TOMEE-4652
A servlet or JSP that leaves a bean-managed
UserTransactionincomplete leaks that transaction to the next request served on the same pooled Tomcat exec thread. The victim request then sees a bogus transaction status — either missing an expectedIllegalStateExceptionor getting aNotSupportedException: Nested Transactions are not supportedon its ownbegin(). Which tests fail depends on which request lands on which thread, which is why the Transactions 2.0 TCK web vehicles (servlet + JSP) fail non-deterministically.Root cause
Geronimo's
TransactionManagerImplkeeps the thread-to-transaction association (and the per-thread transaction timeout) inThreadLocals that are only cleared bycommit()/rollback(). EJBs are wrapped by container interceptors that restore the thread state at the end of the call; plain servlets have no equivalent, andOpenEJBValve's request-teardownfinallyblock cleaned up only the security context. Since Tomcat pools its worker threads, the association survives into the next request.Fix
TransactionCleanup(new) — rolls back and unassociates any transaction still active on the thread at request end, and resets the per-thread transaction timeout (which leaks the same way, since Geronimo only clears it insidebegin()). If the rollback itself fails it falls back tosuspend()so the association never survives the request.finallyinOpenEJBValve(sync path) andOpenEJBSecurityListener.asyncExit()(async complete/error/timeout).CoreUserTransaction.resetError(null)nowremove()s theERRORThreadLocal instead ofset(null), so pooled threads don't keep an empty entry pinned. Separate hygiene issue, not the TCK cause.Testing
UserTransactionLeakTestforces two sequential requests onto a single exec thread (maxThreads=1) and asserts both actually shared the thread (so it can't pass vacuously), that the second request seesSTATUS_NO_TRANSACTION, and that it can still run a transaction of its own.Verified red/green: with the cleanup call removed the test fails with
expected:<[STATUS_NO_TRANSACTION]> but was:<[leaked status 0]>and a follow-upNotSupportedException: Nested Transactions are not supported— matching the issue exactly; with the fix it passes.tomee-catalinaandtomee-embeddedsuites are green.Notes for reviewers
runner-standalone/exclusions/transactions.txtin theapache/tomee-tckharness and rerun the 49-test baseline.maininStatefulBeanManagedTest,InterfaceTransactionTest, andTransactionPropagationTest(confirmed identical on a clean checkout).🤖 Generated with Claude Code