javajakarta-eescheduleejb-3.1job-scheduling

EJB @Schedule wait until method completed


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?


Solution

  • 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.