Svetlin Zarev created TOMEE-2043:
------------------------------------
Summary: Thread local transactions are left open across requests
Key: TOMEE-2043
URL: https://issues.apache.org/jira/browse/TOMEE-2043
Project: TomEE
Issue Type: Bug
Components: TomEE Core Server
Affects Versions: 7.0.3
Reporter: Svetlin Zarev
Attachments: sample.zip
@Transactional CDI bean methods annotated with
@Transactional(dontRollbackOn = SomeException.class) do not commit the
transaction at the end of the request/response cycle when the SomeException
exception is thrown. As a result the thread local transaction object is
preserved across requests which makes unrelated requests to fail with "Nested
transactions are not supported".
Sample application that reproduces the issue is attached to the ticket.
Sample valve that can be used to demonstrate the issue:
{code}
public final class LeakedTransactionDetectionValve extends ValveBase {
private static final Logger logger =
Logger.getLogger(LeakedTransactionDetectionValve.class.getName());
@Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
boolean hasActiveTransaction = false;
try {
final Collection<Transaction> transactionsBeforeRequest =
getTransactions();
for (Transaction transaction : transactionsBeforeRequest) {
if (transaction.getStatus() == Status.STATUS_ACTIVE) {
hasActiveTransaction = true;
break;
}
}
} catch (Exception ex) {
//no-op
}
getNext().invoke(request, response);
if (!hasActiveTransaction) {
try {
final Collection<Transaction> transactionsAfterRequest =
getTransactions();
for (Transaction transaction : transactionsAfterRequest) {
if (transaction.getStatus() == Status.STATUS_ACTIVE) {
logger.log(Level.SEVERE, "Found active transaction: "
+ request.getRequestURI()
+ "?"
+ request.getQueryString()
);
}
}
} catch (Exception ex) {
logger.log(Level.SEVERE, "Failed to determine thread local
transaction status.", ex);
}
}
}
Collection<Transaction> getTransactions() throws NoSuchFieldException,
IllegalAccessException {
final Field threadLocalsField =
Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
final Object threadLocals =
threadLocalsField.get(Thread.currentThread());
final Field tableField =
threadLocals.getClass().getDeclaredField("table");
tableField.setAccessible(true);
final Object table = tableField.get(threadLocals);
final Collection<Transaction> transactions = new LinkedList<>();
for (int i = 0; i < Array.getLength(table); i++) {
final Object entry = Array.get(table, i);
if (null != entry) {
final Field valueField =
entry.getClass().getDeclaredField("value");
valueField.setAccessible(true);
final Object value = valueField.get(entry);
if (value instanceof Transaction) {
transactions.add((Transaction) value);
}
}
}
return transactions;
}
{code}
PS: in addition to the issue above, the
org.apache.openejb.cdi.transactional.InterceptorBase must not wrap the
exception specified in the "donotRollbackOn" attribute inside
TransactionalException
--
This message was sent by Atlassian JIRA
(v6.3.15#6346)