Search code examples
javatimerejbwebsphere-libertyjava-ee-7

EJB Persistent Timer cancel


My application is deployed on WebSphere Liberty 23.0.0.9.

In the following code snippet create method creates timer and cancel method cancels timer. arc represents a value which uniquely identifies the timer.

@Stateless
public class TimerBean {

    @Resource
    private TimerService timerService;

    public void create(String arc, Date expiration) {
        timerService.createSingleActionTimer(expiration, new TimerConfig(arc, true));
    }

    public void cancel(String arc) {
        for (Timer timer: timerService.getTimers()) {
            if (arc.equals(timer.getInfo())) {
                timer.cancel();
            }
    }
}

timerService.getTimers() returns a list of all active timers associated with this bean, but since in my application I potentially have several thousand timers, this for loop can potentially invoke timer.getInfo() several thousand times. The problem is that timer.getInfo() takes approximatelly 5 ms, so canceling just one timer can take several dozen seconds. It seems like whenever timer.getInfo() is invoked, it fetches timer info from the database.

My goal is to decrease the time it takes to cancel one timer.

Given this goal, here are my questions:

  1. Is there a way to fetch only one timer using TimerService? Currently the only way I see timers can be fetched is with timerService.getTimers().
  2. If the only way timers can be fetched is with timerService.getTimers(), is there a more efficient way timer info can be fetched? Currently I am using timer.getInfo().

Solution

  • Unfortunately, the EJB API does not provide a way to fetch a single timer. While the info associated with a timer might be the closest thing to a unique identity, it may be any arbitrary object, and therefore cannot really be stored in a column that would be practical to query. Also, the EJB specification requires that every method call on a timer result in NoSuchObjectLocalException if the timer no longer exists, which generally requires every method call on a timer to access the database.

    However, WebSphere does provide a mechanism to improve efficiency when cached or stale information about a timer is acceptable. See the following article, "Caching data for a timer service". While the article is for WebSphere traditional 9.0.5, the described JVM property is also applicable to Liberty (the Liberty specific version of the article just hasn't been created yet). For Liberty, rather than configuring the property through the administrative console, you would use the jvm.options file; see "Customizing the IBM WebSphere Liberty jvm.options file".

    The com.ibm.websphere.ejbcontainer.allowCachedTimerDataFor JVM property allows you to configure which EJBs allow caching (or all) and even which methods on the Timer interface. For example, if you would like to allow Timer.getInfo() to allow cached data for all EJBs in the server, then you would specify the following in jvm.options:

    -Dcom.ibm.websphere.ejbcontainer.allowCachedTimerDataFor=*=2
    

    The * indicates all EJBs and the number 2 is for the getInfo method. The article provides additional details about what number to use for all methods or other methods and how to limit the caching to specific EJBs.

    When caching is enabled for Timer.getInfo(), then the call to TimerService.getTimers() will include additional internal data in the returned list of timers. Subsequent calls to Timer.getInfo() will then be able to use the state information previously obtained during getTimers(), avoiding an extra trip to the database.

    Please note that that when this property is enabled, even though calls to getInfo() may return a value, a subsequent call to Timer.cancel() may result in a NoSuchObjectLocalException if the timer has already expired for the last time or been cancelled by a different thread.