Skip to content

Challenges with Quartz Integration

rrowlands edited this page Feb 11, 2020 · 7 revisions

The Runway Quartz integration must support the following standard Quartz usecases (and doesn't):

Tracking of Job Status (as reported by Quartz)

Tracking Job status in Quartz is accomplished by registering a listener with the scheduler.

The following Job statuses must be implemented:

  • CANCELED
    • TODO
  • FAILURE
    • This can be handled in a Runway job base class, via a try/catch around the API user's "execute" method.
  • QUEUED
    • TODO
  • RUNNING
    • This can be handled in a Runway job base class (by setting the job status immediately before invoking execute on the job)
  • STOPPED
  • SUCCESS
    • This can be handled in a Runway job base class.
  • WARNING
    • The user of the API should be able to somehow indicate a WARNING status during job execution.

It is important to get the Job status directly from Quartz as there are many events which may occur within Quartz which may cause a Job to not be within a given state that we may otherwise expect.

Job Queueing

Job queueing in Quartz can be implemented in a few different ways.

  1. Using a @DisableConcurrentException (covered in the next section)
  2. By setting the org.quartz.threadPool.threadCount property in quartz.properties. When this property is set, it will limit the maximum number of jobs which can be running at the same time.
  3. JobChainingListener

If a JobDetail is triggered or otherwise scheduled to be run and cannot, due to one of the conditions above, the trigger is said to have MisFired. Quartz provides a TriggerListener interface, which supplies a triggerMisfired listener, however a job being queued and a trigger misfiring are not a 1 to 1 mapping. This can happen, for example, if a trigger misfired, and then elapsed the schedulers 'misfire threshold'.

All this being said, Quartz does NOT have a job queue. Attempting to shoehorn Quartz into doing so has many disadvantages.

Resuming of Scheduler state upon Server Bootup

Quartz has some behavior which must be resumed when the server boots up. Things like resuming of triggers which have misfired. For our purposes, this means that if a job was running when the server shutdown, it should probably be restarted.

Persistence of Metadata driven attributes

Quartz supports database persistence out of the box. It is probably a good idea for us to integrate with this because it allows quartz to do things like process misfired triggers when the server unexpectedly shuts down. It's unclear to me if JNDI is the right integration here.

Additionally, quartz will automatically persist your job's JobDataMap. It's possible we might be able to map this hashmap to a Runway attribute map. It's unclear how this could be done without losing the inherent type-safety of Runway attributes (and allowing for support of nested structures of say like Struct attributes).

Allowing for @DisableConcurrentExecution annotation

This interface, when added to a instance of a Quartz Job interface, will cause the job to either queue (or, if queue time expires, misfire). This is currently impossible to do, because Executable job does not actually implement Quartz's job interface.

Bootstrapping of Runway session and execution context into a Quartz thread

When a Quartz job starts up, it executes in a thread which is managed by Quartz. This means that it is not executing within a Runway request. The job must know what user and dimension it wishes to execute under, as it is not adequate to always execute a job under a SYSTEM session. Additionally, because you cannot create an instance of a Runway class outside of a session, Runway's persistence scheduler class must also be created within this session.

Capturing and handling of Exceptions

When a Quartz job runs, if an exception is thrown during execution that exception must be captured by our Runway scheduler code and a localized message must be stored on the JobHistory. Quartz only supports throwing of JobExecutionException from the execute method, and does not support listening for errors of jobs.

Integration Challenges

  1. The end user must create and manage a class which extends Quartz's Job interface. This is because they must be able to add interfaces (such as DisableConcurrentExecution) to their class.
  2. This class cannot extend a Runway metadata driven class. This is because Quartz must be able to instantiate our class outside of a request, and Runway does not support this.
  3. The end user's class should extend a class within Runway, and the Execute method should be implemented in the base class within Runway. This base class will then invoke a different abstract method which is the "real" execute method (which takes in a JobHistory parameter). This is because we want to wrap the actual end user's execute method within a try/catch so that we can do proper exception handling and tracking of SUCCESS/ERROR state, and also writing of the final Job history.