[ 
https://issues.apache.org/jira/browse/TOMEE-2043?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Svetlin Zarev updated TOMEE-2043:
---------------------------------
    Description: 
@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. Just 
request the application several times and observe how the output is changing.

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


  was:
@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



> 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. Just 
> request the application several times and observe how the output is changing.
> 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)

Reply via email to