0
votes

I have a scheduling singleton where I am trying to manage multiple calendar timers. I have a service that I'm calling every 30 minutes. However, once per day (e.g. 3 AM) I need to call this same service to do something different.

Main question - can you have multiple timers in the same Singleton?

When I look at the code, it seems to me that at some point (3 AM) the two timers will collide and I will get a ConcurrentAccessTimeoutException and one of them won't run. For example:

Caused by: javax.ejb.ConcurrentAccessTimeoutException: WFLYEJB0241: EJB 3.1 PFD2 4.8.5.5.1 concurrent access timeout on TestBean- could not obtain lock within 5000MILLISECONDS

Maybe if I create the 3AM timer first in PostConstruct there will be more likelihood that it will run and lock out the every30 timer? But even if that's possible it seems a) little hokey b) leaving things to chance. I can live with the every30 timer not getting run at 3 AM but the single run one can't miss since it's the only time it gets run. Another option would be to set the every30 timer to something where it wouldn't run at the same time as the once a day timer but again, hokey.

Here is my code:

@Singleton
@Startup
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class SomeScheduler
{

   @Resource
   private TimerService timerService;   
   
   @Inject
   @ConfiguredProperty(key="scheduleExpression1", mandatory=false, defaultValue="minute=*/30; hour=*")
   private ScheduleExpression scheduleExpression1;   
   
   @Inject
   @ConfiguredProperty(key="scheduleExpression2", mandatory=false, defaultValue="hour=3; dayOfWeek=*")
   private ScheduleExpression scheduleExpression2;      
   
   private Timer timer1;
   private Timer timer2;
   
   
   @PostConstruct
   private void postConstruct()
   {
     timer1 = timerService.createCalendarTimer(scheduleExpression1, new TimerConfig("every30", false));
     timer2 = timerService.createCalendarTimer(scheduleExpression2, new TimerConfig("at3AM", false));
   }
      
   @Timeout
   public void handler (Timer timer) 
   { 
     if (Objects.equals(timer.getInfo(), "every30"))            
     {
        // Call Something
     }
     if (Objects.equals(timer.getInfo(), "at3AM"))
     {
        // Call Something else
     }
   } 

}
2
Are the task conflicting and cannot be run at the same time? You could relax the concurrency restrictions, or you could also put a bigger timeout so if two tasks collide it can wait - areus
They are colliding. As an experiment, set TIMER1 to run every two minutes and TIMER2 every 10. At the 10 minute mark, TIMER2 kicked off and I got several ConcurrentAccessTimeoutException messages relating to TIMER1. I will try setting AccessTimeout to something bigger to see if that helps. Part of my question becomes, is having multiple timers in the same class like this considered bad practice? - stackbacker
If you use the default concurrency management of singletons, timeouts can't be processed in parallel. So you need to set a timeout enough for the running task to end, or use "-1". If your tasks can run in parelel, you could use @Lock(READ) - areus

2 Answers

1
votes

Yes you can have multiple timers in the same Singleton.
But you have to turn off "Container managed concurrent access" by annotating your Singleton with:

@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
0
votes

By default, public methods in @Singleton beans has @Lock(WRITE). If you need to run same method multiple times at a same time and you are safe with race condition, then you can add annotation @Lock(READ) on top of the method:

https://docs.oracle.com/cd/E19798-01/821-1841/gipsz/index.html

If you are NOT safe with race condition in this particular method, then you can increase access timeout via annotation: @AccessTimeout

This will solved ConcurrentAccessTimeoutException.