EJB @Schedule wait until method completed
Asked Answered
E

4

38

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?

Excide answered 18/1, 2013 at 15:26 Comment(1)
What application server were you using at the time you experienced this behaviour? I'm using Glassfish 3.1.2.2 and the timer does not work like you describe.Defiant
O
70

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.

Otti answered 19/1, 2013 at 12:44 Comment(5)
no second EJB is needed for your second solution using the AtomicBoolean. The check could also be made within the TimerBean.Unveiling
@Arjan Tijms "In a Singleton methods are by default write-locked" - would you happen to have a reference for that? Seems like a horrible default! As a developer you should make sure your singletons are thread-safe and hence concurrent access (read-lock) should be the default IMHO.Strigil
"If no @Lock annotation is present on the singleton class, the default lock type, @Lock(WRITE), is applied to all business and timeout methods." Link: docs.oracle.com/cd/E19798-01/821-1841/gipsz/index.htmlJackjackadandy
Hi,i am facing the same isseu,i posted here [link] :#67002197 please look what should i do here ?Awfully
"the second bean is needed because EJB 3.1 does not allow upgrading a read lock to a write lock" - It's not so much that it is unsupported, it is inherently impossible to do that bug-free. Whenever two readers concurrently wait to be upgraded, they deadlock each other, because neither can upgrade while the other still holds the lock in read-mode. And giving up the read-lock may simply not be correct. Another solution like the one with two locks is always necessary. Alternatively one could have ONLY a "tryUpgrade" that may fail, but no "upgrade" method which blocksEmmeram
S
9

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

Sue answered 6/12, 2013 at 15:26 Comment(3)
hi, I am also inside the same issue..I tried the first approach but did not solve the issue. can you please provide more information/ example on this and also for the exceptional situation you mentioned in 'doStuff'..?Markley
@NomeshDeSilva, I've updated the code to allow for exceptionsSue
Thanks. Your fix solved my problem. I used your code but I was using createCalendarTimer instead of createSingleActionTimer. After change to createSingleActionTimer all works.Miserly
L
6

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.

Labyrinthodont answered 7/10, 2016 at 10:9 Comment(1)
I think didn't work with Ejb elements, i.e. UserTransaction.Drapery
E
1

well I had a similar problem. There was a job that was supposed to run every 30 minutes and sometimes the job was taking more than 30 minutes to complete in this case another instance of job was starting while previous one was not yet finished. I solved it by having a static boolean variable which my job would set to true whenever it started run and then set it back to false whenever it finished. Since its a static variable all instances will see the same copy at all times. You could even synchronize the block when u set and unset the static variable. class myjob{ private static boolean isRunning=false;

public executeJob(){
if (isRunning)
    return;
isRunning=true;
//execute job
isRunning=false;
  }

}
Estimate answered 12/3, 2015 at 17:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.