EJB @Schedule wait until method completed

Phil P. picture Phil P. · Jan 18, 2013 · Viewed 46k times · Source

I want to write a back-ground job (EJB 3.1), which executes every minute. For this I use the following annotation:

@Schedule(minute = "*/1", hour = "*")

which is working fine.

However, sometimes the job may take more than one minute. In this case, the timer is still fired, causing threading-issues.

Is it somehow possible, to terminate the scheduler if the current execution is not completed?

Answer

Arjan Tijms picture Arjan Tijms · Jan 19, 2013

If only 1 timer may ever be active at the same time, there are a couple of solutions.

First of all the @Timer should probably be present on an @Singleton. In a Singleton methods are by default write-locked, so the container will automatically be locked-out when trying to invoke the timer method while there's still activity in it.

The following is basically enough:

@Singleton
public class TimerBean {

    @Schedule(second= "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() throws InterruptedException {

        System.out.println("Called");
        Thread.sleep(10000);
    }
}

atSchedule is write-locked by default and there can only ever be one thread active in it, including calls initiated by the container.

Upon being locked-out, the container may retry the timer though, so to prevent this you'd use a read lock instead and delegate to a second bean (the second bean is needed because EJB 3.1 does not allow upgrading a read lock to a write lock).

The timer bean:

@Singleton
public class TimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() {

        try {
            workerBean.doTimerWork();
        } catch (Exception e) {
            System.out.println("Timer still busy");
        }
    }

}

The worker bean:

@Singleton
public class WorkerBean {

    @AccessTimeout(0)
    public void doTimerWork() throws InterruptedException {
        System.out.println("Timer work started");
        Thread.sleep(12000);
        System.out.println("Timer work done");
    }
}

This will likely still print a noisy exception in the log, so a more verbose but more silently solution is to use an explicit boolean:

The timer bean:

@Singleton
public class TimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() {
        workerBean.doTimerWork();
    }

}

The worker bean:

@Singleton
public class WorkerBean {

    private AtomicBoolean busy = new AtomicBoolean(false);

    @Lock(READ)
    public void doTimerWork() throws InterruptedException {

        if (!busy.compareAndSet(false, true)) {
            return;
        }

        try {
            System.out.println("Timer work started");
            Thread.sleep(12000);
            System.out.println("Timer work done");
        } finally {
            busy.set(false);
        }
    }

}

There are some more variations possible, e.g. you could delegate the busy check to an interceptor, or inject a singleton that only contains the boolean into the timer bean, and check that boolean there, etc.