Class AdoJobStoreOptions
- Namespace
- Quartz
- Assembly
- Quartz.dll
Strongly typed configuration for the ADO.NET (persistent) job store.
public sealed class AdoJobStoreOptions
- Inheritance
-
AdoJobStoreOptions
- Inherited Members
Remarks
Binds from the JobStore section of the Quartz configuration, and is the typed
replacement for the quartz.jobStore.* property keys. Scheduler identity is not repeated
here — components that need it inject QuartzSchedulerOptions as well, and neither
are the clustering settings, which live once on ClusteringOptions.
Properties
AcceptEnlistedTransactions
Whether the job store may take part in a transaction the application owns, rather than always managing an ADO.NET transaction of its own.
public bool AcceptEnlistedTransactions { get; set; }
Property Value
Remarks
When enabled, the job store uses the connection the application enlisted with EnlistTransaction(IScheduler, DbTransaction) or EnlistConnection(IScheduler, DbConnection, DbTransaction?) for operations on that asynchronous flow, so scheduling either commits with the rest of the application's work or not at all.
Taking part always means handing over a connection. Operations with nothing enlisted keep using a connection of the job store's own, and on the default store — UsePersistentStore(Action<IPersistentStoreBuilder>) without more — that connection is deliberately kept out of any ambient Transaction. The store UseAmbientTransactions() selects is the exception, since running inside a container-managed transaction is that store's contract.
AcquireTriggersWithinLock
Whether trigger acquisition happens inside the database lock.
public bool AcquireTriggersWithinLock { get; set; }
Property Value
CommandTimeout
How long a statement the job store issues may run before the provider cancels it. Left unset, each statement keeps whatever default the ADO.NET provider gives a new command — 30 seconds for most of them.
public TimeSpan? CommandTimeout { get; set; }
Property Value
Remarks
This covers every statement the store issues, including the ones the lock handler takes its row
lock with, which is where a timeout matters most: a node blocked on QRTZ_LOCKS behind a
peer that has stopped without releasing it waits for the provider's default before it can fail
and retry, and the scheduling loop is stalled for all of it.
There is deliberately no per-statement override. A timeout that varies by statement is a way of saying some of the store's work is more expendable than the rest, and none of it is: every statement runs inside a lock the rest of the cluster is waiting on.
ADO.NET's CommandTimeout counts whole seconds, so a value is rounded up to the next second — a configured 1500ms is applied as 2 seconds. Rounding up rather than down keeps a sub-second value from becoming zero, which every provider reads as "wait forever".
DataSource
The name of the data source this job store reads and writes through.
public string DataSource { get; set; }
Property Value
Remarks
Resolves the matching named DataSourceOptions.
DbRetryInterval
How long to wait before retrying after a database failure.
public TimeSpan DbRetryInterval { get; set; }
Property Value
Remarks
The back-off after a failure that was not transient — a database that is down rather than busy. The misfire handler sleeps for it between failing passes. The cluster check-in loop does so only once a failed check-in has spent the window its peers give it (CheckinInterval plus CheckinMisfireThreshold); inside that window it retries sooner, and this value caps how long it may wait between those retries.
DoubleCheckLockMisfireHandler
Whether the misfire handler double-checks the lock before doing work.
public bool DoubleCheckLockMisfireHandler { get; set; }
Property Value
EnlistmentSource
Decides, as each scheduler call that reads or writes the job store begins, whether that call runs inside a transaction the application owns, so that no caller has to enlist it. Code only; left unset, every call keeps using the store's own transaction.
public Func<EnlistmentSourceContext, IDisposable?>? EnlistmentSource { get; set; }
Property Value
Remarks
The delegate is handed an EnlistmentSourceContext, which carries the scheduler. To take part it calls EnlistTransaction(IScheduler, DbTransaction) or EnlistConnection(IScheduler, DbConnection, DbTransaction?) on that scheduler, and returns the IDisposable that ends the enlistment. It returns null when there is nothing to enlist, and the call then uses the store's own transaction. The scheduler disposes what is returned when the call completes, including when it fails or is cancelled.
It runs synchronously, on the caller's thread, before the call's first await. That is the
point of it: state an embedding framework keeps in a thread-static, such as the transaction it
bound to the current thread, is still readable there, and is not after a ConfigureAwait(false)
continuation. It is therefore synchronous by design, and quick: it must not block or call the
scheduler.
Through the container's IScheduler that holds once the scheduler is built: until then each call first awaits the build, and the source runs on whichever thread that await resumes on, where a thread-static is not to be found. The hosted service builds every scheduler when the host starts; otherwise await GetScheduler(CancellationToken) at startup. A source that reads an AsyncLocal<T> or Current is not affected.
It is consulted by every IScheduler member that reads or writes the store - scheduling, unscheduling, rescheduling, adding, deleting and triggering jobs, pausing and resuming, every get, query and exists, the calendar members and Clear(CancellationToken) - and by none of Start(CancellationToken), StartDelayed(TimeSpan, CancellationToken), Standby(CancellationToken), Shutdown(bool, CancellationToken), the status and metadata members, Context, ListenerManager, the execution-limit members and the two interrupts. Every caller is consulted alike: the application, a job through Scheduler, a listener, the HTTP API and the dashboard.
Interrupt(JobKey, CancellationToken) and InterruptFireInstance(string, CancellationToken) are exempt although on a clustered or observer store they do write: an interrupt request is recorded for a firing on another node. It is the scheduler's own signal to a running job, and it must not roll back with the application's transaction.
It is not consulted when the flow already holds an enlistment for this scheduler: an explicit EnlistTransaction(IScheduler, DbTransaction) wins. Nor is it ever offered the scheduler's own work - acquiring, firing and completing triggers, misfire handling, cluster check-in and recovery - which keeps using connections of the store's own.
An enlistment made here is the same enlistment, and meets the same refusals: a provider other than the store's, SQLite with an ambient transaction, and a store that is not a persistent ADO.NET store. They throw from inside the delegate, and the exception reaches the caller unchanged before any store work. An exception from disposing what it returned reaches the caller after a call that succeeded, and never replaces the exception of one that failed.
Disposing an enlistment of a DbTransaction raises the scheduling signal the call deferred, so a scope disposed as the call ends signals before the application has committed, and a trigger due soon may wait up to IdleWaitTime to be seen. To signal after the commit, return a disposable that disposes the enlistment when the transaction completes instead. Calls inside one transaction have to be awaited one at a time.
Start the scheduler outside the application's unit of work. The refusal of a start inside an enlistment
cannot see an enlistment a source made, because that ends with each call, so a
Start(CancellationToken) inside a unit of work that has already written through the source waits
on the TRIGGER_ACCESS lock the application holds, and the application cannot commit while it waits.
Requires AcceptEnlistedTransactions: a store configured with this and without that
fails when the scheduler is built. It is an object, so it has no quartz.* key.
ExecutionHistory
Whether this scheduler keeps its execution history in the database rather than in the process that ran each job.
public bool ExecutionHistory { get; set; }
Property Value
Remarks
Set by UsePersistentStore(store => store.UseExecutionHistory()), which also registers
the store that reads and writes it. Setting it here alone changes only what the schema check
covers, which is why the builder method is the way to ask for this.
It is on this options type because it is a statement about the schema: the history's three
tables and their columns — from database/migrations/4.2/add_execution_history_<dialect>.sql,
4.3/add_execution_log and add_misfire_reason, and 4.4/add_execution_outcome — are
optional, so startup probes for them only when this says they are in use, and names the oldest
script missing. A database without them goes on working untouched while the history is off.
IsTransient
An extra answer to "is this failure worth retrying", for a driver that reports a retryable condition Quartz does not recognise.
public Func<Exception, bool>? IsTransient { get; set; }
Property Value
Remarks
Consulted first, and only additive: returning false — which is also what
leaving this unset means — falls through to the built-in classification, so a predicate cannot
make Quartz stop retrying something it already retries. That is deliberate. The built-in list is
IsTransient, a SQLSTATE in class 40, SQL Server's transient
error numbers, SQLite's busy and locked codes and TimeoutException, over the whole
chain of inner exceptions; a driver that misreports one of those is a bug to fix in
TransientErrorDetector rather than to work around here.
The exception handed over is the store's own, so the driver's exception is usually its InnerException — GetBaseException() is the short way to the one your driver threw.
It is called on the failure path of a store operation, while a lock may be held, so it must be quick and must not throw: a predicate that throws would replace the database failure the store was about to report with one from the classification of it.
LockFreeFiringRounds
Whether a firing round whose every trigger may run beside itself takes no TRIGGER_ACCESS
lock. Defaults to true.
public bool LockFreeFiringRounds { get; set; }
Property Value
Remarks
A round decides from its first read. It takes the lock when a candidate's job disallows concurrent
execution, when a candidate's overlap policy is Skip, BufferOne or
CancelPrevious, when the request counts against a cluster-scoped execution limit, when the
store's transactions run above read committed, when AcquireTriggersWithinLock is on,
on SQLite, and — on a cluster — while any live node is older than 4.6 or no check-in has read the
state table yet. Otherwise the claim is a compare-and-swap on each row's fire time, and the fire
writes rows this transaction holds. A round of one trigger that takes the lock takes it as 4.5 did.
Under contention, rounds take the lock until the backlog clears: a round that lost a claim to another
node makes the rounds after it locked, reason contention, until a locked round comes back short
or two seconds pass.
On SQL Server since 4.7, only in a cluster of two or more live 4.7 nodes, and only while the database
has READ_COMMITTED_SNAPSHOT on, which the store reads once at startup. Otherwise firing rounds
work as with this option off, reason no_live_peer or read_committed_snapshot_off: a single
node has no contention for a round without the lock to save. A SQL Server round that takes no lock
claims in one transaction and fires in a second
(#4117).
false restores 4.5: a round of one acquires without the lock and the scheduler fires it under the lock in a second transaction, and every other round is locked throughout.
LockOnInsert
Whether a lock is taken when inserting new job or trigger rows.
public bool LockOnInsert { get; set; }
Property Value
LockWaitWarningThreshold
How long one attempt to take a job store lock may go on before it is reported as slow, or null to report nothing however long a lock takes. Defaults to 30 seconds.
public TimeSpan? LockWaitWarningThreshold { get; set; }
Property Value
Remarks
A lock statement that is blocked throws nothing and returns nothing, so a node waiting behind a
peer that stopped without releasing QRTZ_LOCKS — or behind a database session whose client
is gone and which the server has not yet cleaned up — stops scheduling in complete silence: no
retry runs, no failure is counted, and no listener is told. This is what breaks the silence. One
warning is logged per acquisition, under event id 3716, and it names the lock, how long the wait
has lasted and which requestor is waiting.
It reports and nothing more: the wait is still the database's to end, and CommandTimeout — or a wait timeout written into the lock statement itself, through SelectWithLockSql — is what bounds it. Both are worth setting; this one only makes sure the operator finds out.
Zero is refused rather than read as "warn immediately". Every acquisition takes some time, so a zero threshold is a warning per lock taken, which is the same as no signal at all; "report nothing" is spelled null.
MaxConsecutiveFireFailures
How many fires of one trigger in a row may fail before the trigger is stored ERROR, or
0 to never store it ERROR for this. Defaults to 5.
public int MaxConsecutiveFireFailures { get; set; }
Property Value
Remarks
A fire that fails for a reason a retry will not cure — a constraint violation, a column the
database no longer has — is rolled back alone, and the trigger is released and acquired again.
One that fails every time was acquired again forever: each round cost a rolled-back transaction,
and a DisallowConcurrentExecutionAttribute job's other triggers never fired behind
it. After this many failures in a row the trigger is stored ERROR, the scheduler listeners
hear TriggerInError(IScheduler, TriggerKey, CancellationToken), and event 3050 is logged.
ResetTriggerFromErrorState(TriggerKey, CancellationToken) brings it back once the cause is fixed.
A misfire whose ICalendar, or a trigger type of your own, throws counts as a failed fire too, at most once per MisfireThreshold, and logs event 3603 each time. It is not counted inside a transaction the application owns. A misfire handled ends the run.
Only such failures count. A transient one is retried, a failure of the whole batch fails every
trigger in it alike, and a database that is down fails acquisition before any fire — none of those
is counted, so an outage parks nothing. A fire that commits, on this node or another, starts the
count again. The count is each node's own, so in a cluster a trigger may fail this many times on
every node before one of them stores it ERROR.
A failure your driver reports that is really transient belongs in IsTransient, which retries it instead of counting it.
MaxMisfiresToHandleAtATime
The maximum number of misfired triggers handled in a single pass.
public int MaxMisfiresToHandleAtATime { get; set; }
Property Value
MaxTransientRetries
The number of times a transient database failure is retried.
public int MaxTransientRetries { get; set; }
Property Value
MisfireHandlerFrequency
How often the misfire handler runs. Defaults to MisfireThreshold when unset.
public TimeSpan? MisfireHandlerFrequency { get; set; }
Property Value
MisfireThreshold
How far past its scheduled fire time a trigger may be before it is considered misfired.
public TimeSpan MisfireThreshold { get; set; }
Property Value
OpenConnection
Whether the ambient-transaction store opens the connections it creates before handing them to an operation. Defaults to false, leaving the opening to the externally managed transaction.
public bool OpenConnection { get; set; }
Property Value
Remarks
Read only by that store, which is the one
UseAmbientTransactions() selects — or
quartz.jobStore.type = Quartz.Impl.AdoJobStore.ExternalTransactionJobStore, Quartz. It
says nothing on the default local-transaction store.
RecoverFiringsCancelledByShutdown
Whether a firing that a shutdown interrupts is handed back for recovery, rather than completed as cancelled, when its job RequestsRecovery. Defaults to false.
public bool RecoverFiringsCancelledByShutdown { get; set; }
Property Value
Remarks
A job interrupted by ShutdownJobInterruption that stops by throwing OperationCanceledException is completed by storing a recovery trigger, in the same transaction, as cluster recovery would after a crash. Another node fires it on its next acquisition, or this node does when it starts again; the job sees Recovering.
Nothing else changes. A job that is not asked to stop, or that returns when it is, has finished. An Interrupt(JobKey, CancellationToken) is still a cancellation. The firing is not a retry attempt and writes no job data, the triggers awaiting it wait on the recovery trigger instead, and its execution history row is Cancelled with a summary saying it was handed back.
RetryableActionErrorLogThreshold
How many consecutive failures of a retryable action occur before they are logged as errors.
public int RetryableActionErrorLogThreshold { get; set; }
Property Value
SchemaProvisioning
What the store does about its schema at startup: nothing, verify it, or create what is missing and then verify it.
public SchemaProvisioning SchemaProvisioning { get; set; }
Property Value
Remarks
SelectWithLockSql
Overrides the SQL statement used to acquire the row lock. Defaulted for SQL Server to its
WITH (UPDLOCK,ROWLOCK) form.
public string? SelectWithLockSql { get; set; }
Property Value
Remarks
Read only when the job store builds a database-locking handler for itself, which is what it does
when UseDbLocks is on — through clustering, enlisted transactions, or the setting
itself — and no lock handler was supplied. A handler chosen with UseLockHandler takes its
statement through its own constructor, and the store logs a warning at startup if this is set as
well, since the value would silently do nothing.
The statement must select the row in {0}LOCKS matching the @schedulerName and
@lockName parameters, taking whatever lock the database needs to serialize the callers;
{0} is the table prefix.
StoreJobDataAsStrings
When true, job data maps are persisted as name/value string pairs rather than serialized objects, which keeps stored data readable and version tolerant.
public bool StoreJobDataAsStrings { get; set; }
Property Value
Remarks
This was UseProperties, which read as a verb and collided with
QuartzSchedulerBuilder.UseProperties and AddQuartz(properties) — an unrelated thing
about flat quartz.* configuration keys. The flat key that sets it is still
quartz.jobStore.useProperties.
TablePrefix
The prefix applied to every Quartz table name. Defaults to DefaultTablePrefix.
public string TablePrefix { get; set; }
Property Value
TransactionIsolationLevel
The isolation level the job store begins its own transactions at. Left unset, they are ReadCommitted.
public IsolationLevel? TransactionIsolationLevel { get; set; }
Property Value
Remarks
This replaced a TxIsolationLevelSerializable flag, which could say only "serializable" or
"the default" and so could not express Snapshot,
RepeatableRead or a deliberate
ReadUncommitted. null means Quartz's default rather
than the provider's, because those differ — MySQL's is repeatable read — and inheriting the
provider's would silently change how the store behaves depending on which database it is talking
to.
SQLite is the exception: the store forces serializable there whatever this says, because concurrent SQLite transactions at a lower level fail with "database is locked".
This applies only to connections the job store opens itself. An operation running on a connection the application enlisted uses whatever isolation level that transaction was begun at.
TransientRetryInterval
How long to wait between transient database failure retries.
public TimeSpan TransientRetryInterval { get; set; }
Property Value
UseBackgroundThreads
Whether the misfire handler and cluster manager run on background threads, which do not keep the process alive on their own.
public bool UseBackgroundThreads { get; set; }
Property Value
Remarks
These two are the only real threads Quartz creates — the scheduling loop is a
Task — so this is the whole of the "do Quartz's threads
hold my console application open" question. UseBackgroundThreads rather than
MakeThreadsDaemons: "daemon" is the Java word for what .NET calls
IsBackground.
UseDbLocks
Whether database row locks are used for synchronization. Required for clustering.
public bool UseDbLocks { get; set; }