Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 301
Phil P. Avatar asked Jan 18 '13 15:01

Phil P.


3 Answers

I ran into the same problem but solved it slightly differently.

@Singleton
public class DoStuffTask {

    @Resource
    private TimerService timerSvc;

    @Timeout
    public void doStuff(Timer t) {
        try {
            doActualStuff(t);
        } catch (Exception e) {
            LOG.warn("Error running task", e);
        }
        scheduleStuff();
    }

    private void doActualStuff(Timer t) {

        LOG.info("Doing Stuff " + t.getInfo());
    }

    @PostConstruct
    public void initialise() {
        scheduleStuff();
    }

    private void scheduleStuff() {
        timerSvc.createSingleActionTimer(1000l, new TimerConfig());
    }

    public void stop() {
        for(Timer timer : timerSvc.getTimers()) {
            timer.cancel();
        }
    }

}

This works by setting up a task to execute in the future (in this case, in one second). At the end of the task, it schedules the task again.

EDIT: Updated to refactor the "stuff" into another method so that we can guard for exceptions so that the rescheduling of the timer always happens

like image 31
drone.ah Avatar answered Nov 06 '22 20:11

drone.ah


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.

like image 178
Arjan Tijms Avatar answered Nov 06 '22 19:11

Arjan Tijms


Since Java EE 7 it is possible to use an "EE-aware" ManagedScheduledExecutorService, i.e. in WildFly:

In for example a @Singleton @Startup @LocalBean, inject the default "managed-scheduled-executor-service" configured in standalone.xml:

@Resource
private ManagedScheduledExecutorService scheduledExecutorService;

Schedule some task in @PostConstruct to be executed i.e. every second with fixed delay:

scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);

scheduleWithFixedDelay:

Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next.[...]

Do not shutdown the scheduler in i.e. @PreDestroy:

Managed Scheduled Executor Service instances are managed by the application server, thus Java EE applications are forbidden to invoke any lifecycle related method.

like image 6
Torsten Römer Avatar answered Nov 06 '22 21:11

Torsten Römer