Cancel ejb timer on rollback

2.4k views Asked by At

Is there any way to make sure a periodic (every 10 seconds) and persistent timer is cancelled when an exception occurs? The implementation of the @Timeout method is something like this (simplified from legacy code):

@Timeout
@TransactionAttribute(REQUIRES_NEW)
public void onTimeout(Timer timer) {
    try {
        doSomeBusinessLogic();
    } catch (Exception e) {
        // throwing this exception makes sure rollback is triggered
        throw new EJBException(e);
    }
}

Upon any exception in doSomeBusinessLogic(), its transaction needs to be rolled back. This is working fine. However, I would also make sure that the timer is cancelled.

The straightforward solution is to put timer.cancel() in the catch block. This is not working, however, because the cancelling will also be rolled back (JEE6 Turorial):

An enterprise bean usually creates a timer within a transaction. If this transaction is rolled back, the timer creation also is rolled back. Similarly, if a bean cancels a timer within a transaction that gets rolled back, the timer cancellation is rolled back. In this case, the timer’s duration is reset as if the cancellation had never occurred.

How can I make sure that the timer is cancelled (preventing any further timeouts) if an exception/rollback occurs? Setting a maximum nuber of retries would also be sufficient, but I don't think this is supported by JBoss.

Application server is JBoss AS 7.2.

3

There are 3 answers

8
slwk On BEST ANSWER

I also tried solution proposed by Sergey and it seems to work - timer is cancelled. Tested on JBoss EAP 6.2. Here is the code I used for testing:

@Stateless
public class TimeoutTest implements TimeoutTestLocal {

@Resource
TimerService timerService;

@Resource
SessionContext sessionContext;

@Timeout
@TransactionAttribute(TransactionAttributeType.NEVER)
public void tmout(javax.ejb.Timer timer) {
    try {
        System.out.println("timout invoked");
        //instead of internal call let's invoke doNothing as
        //this timeout callback is client of TimeoutTest EJB
        //in this way doNothing will be run inside transaction
        TimeoutTestLocal local = sessionContext.getBusinessObject(TimeoutTestLocal.class);
        local.doNothing();  
    } catch (Exception e) {
        timer.cancel();
        System.out.println("Timer cancelled");
    }
}

@Override 
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void doNothing() {
    throw new EJBException("RE Exception");
}


@Override
public void schedule() {
    timerService.createTimer(5000, 10000, "test");
}
}
0
Sergei Batsura On
  1. You can create new transation (call next EJB) from timer bean
  2. You can change TA @TransactionAttribute(NEVER) and create new transation (call next EJB) from timer bean

And put timer.cancel() in the catch block

0
IonutB On

I have a similar situation, however canceling the timer in the catch clause seems to work. The reason why I need this is to force the container (Wildfly) to not retry a failed timeout.

The code looks something like this:

@Timeout
public void onTimeout(Timer timer) {
    try {
        //the other ejb has @TransactionAttribute(TransactionAttributeType.SUPPORTS)
        doSomeBusinessLogicInSomeOtherEjbThatThrowsEjbException();
    } catch (EJBException e) {
        timer.cancel();
        throw e;//this is not necessary since the EJB context has already getRolledBack = true at this point
    }
}