1

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.

Magnilex
  • 11,584
  • 9
  • 62
  • 84

3 Answers3

3

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");
}
}
slwk
  • 587
  • 3
  • 7
  • Yes, this solution properly cancels the timer, had something similar in my mind as well. However, when applied to my code, what is executed in what corresponds to `doNothing()` is not rolled back. This could be because of my more complex scenario with chained EJB calls. I agree that this should work. – Magnilex Nov 18 '14 at 08:08
  • Are you sure your method throws system exception? Please remember that by default application exceptions do not rollback transactions. – slwk Nov 18 '14 at 18:22
  • Yes, for testing purpose I am throwing an EJBException (which extends RuntimeException) directly from my code. As I said, I also expect a rollback, but it is actually not occuring. Having said that, I would say that this answer is correct. – Magnilex Nov 19 '14 at 08:19
  • This is strange. When non-application exception is thrown then transaction should be rolled back as sepcification says. What symptoms do you experience by saying that it is not rolled back? E.g. data are commited to DB? – slwk Nov 19 '14 at 09:26
  • I agree with that it is strange. Yes, the symptoms is that the database is in fact updated. There are some chained EJB calls, but none of them should start a new transaction. On the other hand, this is old, quite hairy, legacy code, so I have to dig deeper into this. The timmer is cancelled correctly however. – Magnilex Nov 19 '14 at 11:24
  • If you extend your question with information that the `doNothing()` method needs to be called "EJB" style to trigger the new transaction, I will accept this answer. What I mean is that, if the method is called from the same class, without an EJB proxy, the `@TransactionalAttribute` annotation will have no effect. – Magnilex Nov 25 '14 at 09:50
  • Your right about inter-bean invocation. I think you can use something like that: `TimeoutLocal local = sessionContext.getBusinessObject(TimeoutLocal.class); local.doNothing();` And make `doNothing()` method part of your business interface - public. – slwk Nov 25 '14 at 12:30
  • Yeah, I know what to do. It would be nice if you would edit your answer to include som short info that an actual EJB call is needed to trigger the new transaction. Then I will accept your answer. – Magnilex Nov 25 '14 at 13:06
2
  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

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
    }
}
IonutB
  • 71
  • 1
  • 6