Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wait in Main Thread for Quartz Scheduler to Finish

I have a Java application that makes use of a Quartz Scheduler in the guise of a SchedulerFactoryBean. The main() method gets the application context, retrieves the root bean, and commences scheduling jobs.

The issue is that the Scheduler runs in its own thread, so when the main thread is done submitting jobs, it returns and the Scheduler goes on without it. When the Scheduler is finally done (or even if you explicitly call shutdown() on it), the application just hangs there for all eternity.

I have two solutions:

  1. Keep track of the job/trigger count, incrementing it whenever you add a job to the Scheduler. Attach a simple SchedulerListener to the Scheduler that decrements this count with every call to triggerFinalized(), and set up a while loop with a Thread.sleep() inside it that constantly checks to see if the count has hit 0. When it does, it will return up to the main() method and the application will exit normally.
  2. Take the custom SchedulerListener from option 1, and keep track of the job count inside of it. Increment for every call to jobAdded(), and decrement for every call to triggerFinalized(). When the count hits 0, call shutdown() on the Scheduler (or not, it doesn't actually matter) and then call System.exit(0).

I have implemented both of these independently in turn, so I know they both actually function. The problem is that they are both terrible. An infinite while loop polling a value? System.exit(0)? Bleargh.

Does someone have a better way, or are these seriously my only options here?

Edit: While thinking about this on the way home, I came to the conclusion that this may be caused by the fact that I'm using SchedulerFactoryBean. This auto-starts when Spring initializes the application context - that seems to put it outside the scope of the main thread. If I went with a slightly different Scheduler that I manually initialized and called start() on in the code, would this run the Scheduler in the main thread, thus blocking it until the Scheduler completes running all jobs? Or would I still have this problem?

Edit: Son of a...http://quartz-scheduler.org/documentation/quartz-2.x/examples/Example1

To let the program have an opportunity to run the job, we then sleep for 90 seconds. The scheduler is running in the background and should fire off the job during those 90 seconds.

Apparently, that will not work, because the scheduler seems to always run in the background.

like image 970
Random Human Avatar asked May 24 '13 23:05

Random Human


2 Answers

In your SchedulerListener add an object solely for synchronization and locking. Call it exitLock or something. You main thread retrieves the scheduler, sets up the listener, submits all the jobs and then just before returning executes

Object exitLock = listener.getExitLock();
synchronized (exitLock) {
    exitLock.wait(); // wait unless notified to terminate
}

On every triggerFinalized() call your listener decrements the counter for pending jobs. Once all the jobs have finished executing your listener shuts the scheduler down.

if (--pendingJobs == 0)
    scheduler.shutdown(); // notice, we don't notify exit from here

Once the scheduler shuts down it invokes one last callback on the listener where we notify the main thread to terminate and hence the program exits gracefully.

void schedulerShutdown() {
    // scheduler has stopped
    synchronized (exitLock) {
        exitLock.notify(); // notify the main thread to terminate
    }
}

The reason we didn't notify in triggerFinalized() when all the pending jobs were finished is that in case the scheduler was shutdown prematurely and not all the jobs were finished we would have left our main thread hanging. By notifying in response to the shutdown event we make sure our program exits successfully.

like image 178
Ravi K Thapliyal Avatar answered Sep 24 '22 15:09

Ravi K Thapliyal


I think here can be another solution.

Key points:

  1. When task was executed the last time context.getNextFireTime() returns null.
  2. Scheduler.getCurrentlyExecutingJobs == 1 indicate that it is the last executed job.

So when point 1 and 2 is true we can shutdown Scheduler and call System.exit(0). Here is the code:

Listener

public class ShutDownListenet implements JobListener {
    @Override
    public String getName () { return "someName";    }
    @Override
    public void jobToBeExecuted (JobExecutionContext context) {}
    @Override
    public void jobExecutionVetoed (JobExecutionContext context) {}

    @Override
    public void jobWasExecuted (JobExecutionContext context, JobExecutionException jobException) {
        try {
            if (context.getNextFireTime() == null && context.getScheduler().getCurrentlyExecutingJobs().size() == 1) {
                context.getScheduler().shutdown();
                System.exit(0);
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

Code in the main function public static void main (String[] args) { Trigger trigger = ... Job job = ...

    JobListener listener = new ShutDownListenet();  
    scheduler.getListenerManager().addJobListener(listener);

    scheduler.scheduleJob(job, trigger);
}

NOTE

  1. I do not write synchronized blocks, but I tested this code with 100 concurent jobs, it works.
  2. Did not tested in "complex" enviroment: clusters or RMI. (behavior can be differ).

Any comments are wellcome.

like image 28
Cherry Avatar answered Sep 23 '22 15:09

Cherry