The CMTTxInterceptor is responsible for starting a transaction if a public method is called on a session bean, and for ending it (commit or rollback) after the method has returned. If a runtime exception occurs during the execution of the public method, WildFly normally throws an EJBException with the original exception as cause. However, if the transaction has timed out, it swallows the original exception, and throws a EJBTransactionRolledBackException without a root cause. That obscures what actually happened during the execution of the public method.
I attached a zip with an small Arquillian project that shows (1) what happens in case of an exception in combination with an active transaction (the succeeding test) and (2) what happens if the exception occurs when the transaction has timed out (the failing test).
I think that the exception swallow is explainable as follows, referring to the 10.0.0.Final version of CMTTxInterceptor, that can be found here
This happens (I verified by stepping through the code with the debugger):
- The catch block of invokeInOurTx is entered.
- The method handleExceptionInOurTx tries to set the "rollback only" status on the transaction. The result is that the transaction, which has already been rolled back, stays in the rolled back state. A new EJBException is thrown, wrapping the original runtime exception.
- The finally block of invokeInOurTx is entered.
- The endTransaction method sees that the transaction is in the rolled back state, and concludes that the "reaper canceled (rolled back) tx case" is applicable. It throws a new exception (which replaces the EJBException that just has been thrown), but that new exception doesn't have a cause.
The desired behavior would be that the interceptor only wraps the original exception in an EJBException in this case. I think that the invokeInOurTx method should only call the endTransaction method, if the transaction is not in the rolled back state.