|
|
|
I am now seeing this behavior in my application, and believe that it is the result of a race condition. In my case, I am calling shutdown with waitForJobsToComplete=true, but am occasionally seeing a call to releaseAcquiredTrigger on a job store that has been shutdown.
What I think is happening is that the shutdown method of QuartzScheduler is considering QuartzSchedulerThread to have stopped running after the call to QuartzSchedulerThread.halt(). However, it can be the case that at the time halt() is called, the QuartzSchedulerThread is actually sleeping at line 273. If QuartzScheduler is able to call resource.getJobStore().shutdown() before that sleep completes, then the QuartzSchedulerThread can end up calling releaseAcquiredTrigger on a shut down job store, which does not seem correct. I think the proper fix is to change QuartzSchedulerThread.halt() to return only once the thread has actually stopped executing, or else have QuartzScheduler check for Thread.isAlive() to return false before continuing. Thanks for finding this case Zach!
What I am leaning towards is having the QuartzScheduler call join() on the QuartzSchedulerThread after the call to halt so shutdown() won't continue until the QuartzSchedulerThread is dead. (this would basically be the same as your "check for Thread.isAlive() to return false before continuing" suggestion) try { schedThread.join(); } catch (InterruptedException ignore) { } I added the join as described above.
I went back and forth on whether to pass a timeout to join() and ultimately decided against it since, if it required a timeout, it would imply a bug in the QuartzSchedulerThread, and I'd rather not risk masking such a bug if it were to exist. | |||||||||||||||||||||||||||||||||||||||||
If you want all of your currently firing triggers to complete before shutdown completes, you should call Scheduler.shutdown(true) (waitForJobsToComplete).
Otherwise, the assumption is that you want a hard shutdown of Quartz, and if you want special behavior in this case you can set the recovery flag on your JobDetails and handle each case as defined by your application's need.