docs/documentation/best-practices.md
This document was adapted from Quartz Java
Only store primitive data types (including strings) in JobDataMap to avoid data serialization issues short and long-term.
The JobDataMap that is found on the JobExecutionContext during Job execution serves as a convenience.
It is a merge of the JobDataMap found on the JobDetail and the one found on the Trigger, with the value in the latter overriding any same-named values in the former.
Storing JobDataMap values on a Trigger can be useful in the case where you have a Job that is stored in the scheduler for regular/repeated use by multiple Triggers, yet with each independent triggering, you want to supply the Job with different data inputs.
In light of all of the above, we recommend as a best practice the following: Code within the IJob.Execute(..) method should generally retrieve
values from the JobDataMap on found on the JobExecutionContext, rather than directly from the one on the JobDetail.
TriggerUtils:
Writing scheduling data directly to the database (via SQL) rather than using scheduling API:
If you point more than one scheduler instance at the same set of database tables, and one or more of those instances is not configured for clustering, any of the following may occur:
It is recommended that your Datasource max connection size be configured to be at least the number of worker threads in the thread pool plus three. You may need additional connections if your application is also making frequent calls to the scheduler API.
NOTE: Specifics of the transition hour and the amount of time the clock moves forward or back varies by locale see: https://secure.wikimedia.org/wikipedia/en/wiki/Daylight_saving_time_around_the_world.
SimpleTriggers are not affected by Daylight Savings Time as they always fire at an exact millisecond in time, and repeat an exact number of milliseconds apart.
Because CronTriggers fire at given hours/minutes/seconds, they are subject to some oddities when DST transitions occur.
As an example of possible issues, scheduling in the United States within TimeZones/locations that observe Daylight Savings time, the following problems may occur if using CronTrigger and scheduling fire times during the hours of 1:00 AM and 2:00 AM:
Again, specifics of time and amount of adjustment varies by locale.
Other trigger types that are based on sliding along a calendar (rather than exact amounts of time), such as CalenderIntervalTrigger, will be similarly affected - but rather than missing a firing, or firing twice, may end up having it's fire time shifted by an hour.
Long-running jobs prevent others from running (if all threads in the ThreadPool are busy).
If you feel the need to call Thread.sleep() on the worker thread executing the Job, it is typically a sign that the job is not ready to do the rest of its work because it needs to wait for some condition (such as the availability of a data record) to become true.
A better solution is to release the worker thread (exit the job) and allow other jobs to execute on that thread. The job can reschedule itself, or other jobs before it exits.
A Job's execute method should contain a try-catch block that handles all possible exceptions.
If a job throws an exception, Quartz will typically immediately re-execute it, meaning the job can and likely will throw the same exception again. This can lead to wasted resources and, in the worst cases, unstable or crashed applications. It's better if the job catches all exceptions it may encounter, handles them, and reschedules itself or other jobs to work around the issue.
In-progress Jobs marked "recoverable" are automatically re-executed after a scheduler fails. This means some of the job's "work" will be executed twice.
This means the job should be coded in such a way that its work is idempotent.
Performing large amounts of work is discouraged, as the thread that would be executing the job (or completing the trigger and moving on to firing another job, etc.) will be tied up within the listener.
Every listener method should contain a try-catch block that handles all possible exceptions.
If a listener throws an exception, it may cause other listeners not to be notified and/or prevent the execution of the job, etc.
Some users expose Quartz's Scheduler functionality through an application user interface. This can be very useful, though it can also be extremely dangerous.
Be sure you don't mistakenly allow users to define jobs of any type they wish, with whatever parameters they wish.
For example, Quartz.Jobs package ships with a pre-made job NativeJob, which will execute any arbitrary native (operating system) system command that it is defined to.
Malicious users could use this to take control of, or destroy your system.
Likewise other jobs such as SendEmailJob, and virtually any others could be used for malicious intent.
Allowing users to define whatever job they want effectively opens your system to all sorts of vulnerabilities comparable/equivalent to Command Injection Attacks as defined by OWASP and MITRE.