Class DbLockHandler
- Namespace
- Quartz.Impl.AdoJobStore
- Assembly
- Quartz.dll
Base class for database based lock handlers for providing thread/resource locking in order to protect resources from being altered by multiple threads at the same time.
public abstract class DbLockHandler : ILockHandler
- Inheritance
-
DbLockHandler
- Implements
- Derived
- Inherited Members
Constructors
DbLockHandler(string, string?, string, string, IDbProvider)
Initializes a new instance of the DbLockHandler class.
protected DbLockHandler(string tablePrefix, string? schedulerName, string sql, string insertSql, IDbProvider dbProvider)
Parameters
tablePrefixstringThe table prefix.
schedulerNamestringthe scheduler name
sqlstringThe statement that takes the lock.
insertSqlstringThe statement that inserts the lock row when it does not exist yet.
dbProviderIDbProviderThe db provider.
Remarks
The two statements are fixed at construction. They were settable, which meant a subclass could swap them after the table prefix had already been folded in, and the two halves of the lock - the select and the insert that backs it - could disagree about which table they were talking to.
Properties
InsertSql
The statement that inserts the lock row when it does not exist yet, before the table prefix is folded in.
protected string InsertSql { get; }
Property Value
LockSql
The statement that takes the lock, before the table prefix is folded in.
protected string LockSql { get; }
Property Value
RequiresConnection
This lock handler does use the database.
public bool RequiresConnection { get; }
Property Value
SchedulerName
Name of the scheduler whose lock rows this handler contends for, told to the handler through Initialize(LockHandlerContext).
public string? SchedulerName { get; }
Property Value
TablePrefix
Table prefix of the tables the ADO.NET job store uses, told to the handler through Initialize(LockHandlerContext).
public string TablePrefix { get; }
Property Value
TimeProvider
The clock this handler backs off on between attempts, told to it through Initialize(LockHandlerContext). Defaults to System for a handler used before the store has initialized it.
protected TimeProvider TimeProvider { get; }
Property Value
Methods
AcquireLock(Guid, ConnectionAndTransactionHolder?, SchedulerLock, CancellationToken)
Grants a lock on the identified resource to the calling context, waiting until it is available.
public ValueTask<bool> AcquireLock(Guid requestorId, ConnectionAndTransactionHolder? conn, SchedulerLock lockKind, CancellationToken cancellationToken = default)
Parameters
requestorIdGuidIdentifies the calling context, so that a re-entrant acquire can be told from a competing one.
connConnectionAndTransactionHolderThe unit of work to take the lock on. It is null when the store has not opened a connection yet, which it delays for a handler whose RequiresConnection is false.
lockKindSchedulerLockWhich of the two locks to take.
cancellationTokenCancellationTokenThe cancellation instruction.
Returns
- ValueTask<bool>
true if this call took the lock and its caller must release it; false if
requestorIdalready held it and must not.
Remarks
The answer is a release obligation rather than a report of success. true
says this call took the lock and its caller is the one that has to give it back;
false says requestorId already held it — a re-entrant
acquire — so the outer caller's single release is what frees it, and releasing here would
drop a lock that operation is still relying on. A handler that instead counts its holds may
answer true to a re-entrant acquire, because there the inner release is
the one that decrements; either way the caller releases exactly when it was told
true.
Re-entry is the only thing false may mean. A handler that could not
take the lock says so by throwing: LockException when the lock was refused,
and OperationCanceledException when
cancellationToken fired. Answering false on
cancellation is not a harmless approximation — the job store reads it as "already held, do
not release" and goes on to run the guarded operation with no lock at all, which is the whole
point of taking one.
The store's own ordering must not be relied on to cover for a handler that gets this wrong. A handler answering false to RequiresConnection is followed immediately by a connection open on the same token, so a mistake here is masked today by that open throwing first. That is an accident of statement order rather than a guarantee, and it does not hold wherever the store reaches the lock in a different sequence.
A handler that gives up leaves nothing behind: an abandoned wait must not consume a handover meant for the next waiter, and a partially taken lock is released before the exception escapes, so that the next caller can still be served.
Exceptions
- OperationCanceledException
cancellationTokenfired before the lock was taken.- LockException
The lock could not be obtained.
AddCommandParameter(DbCommand, string, object?)
Binds a parameter to a command prepared by PrepareCommand(ConnectionAndTransactionHolder, string), rewriting the
statement's @name placeholder for drivers that do not use @ or that bind by
position.
protected void AddCommandParameter(DbCommand command, string paramName, object? paramValue)
Parameters
commandDbCommandThe command to bind to.
paramNamestringName of the parameter, without the driver's prefix.
paramValueobject
Remarks
There is no overload taking a provider-specific data type or a size, because a lock statement binds a scheduler name and a lock name and both are strings. A handler that needs to bind something else is not locking a Quartz lock row.
ExecuteSql(Guid, ConnectionAndTransactionHolder, string, string, string, CancellationToken)
Execute the SQL that will lock the proper database row.
protected abstract ValueTask ExecuteSql(Guid requestorId, ConnectionAndTransactionHolder conn, string lockName, string expandedSql, string expandedInsertSql, CancellationToken cancellationToken = default)
Parameters
requestorIdGuidconnConnectionAndTransactionHolderlockNamestringexpandedSqlstringexpandedInsertSqlstringcancellationTokenCancellationToken
Returns
Initialize(LockHandlerContext)
Learns which scheduler this handler locks for and folds the store's table prefix into both statements. The job store calls this once before the handler is used, whether the store built the handler itself or the container supplied it.
public void Initialize(LockHandlerContext context)
Parameters
contextLockHandlerContext
Remarks
The command timeout arrives here too, so the accessor is rebuilt rather than reconfigured: it is only ever replaced on this one call, before any lock has been taken.
PrepareCommand(ConnectionAndTransactionHolder, string)
Prepares one of this handler's statements against the unit of work ExecuteSql(Guid, ConnectionAndTransactionHolder, string, string, string, CancellationToken) was handed, attached to its connection and transaction and carrying the store's command timeout.
protected DbCommand PrepareCommand(ConnectionAndTransactionHolder conn, string commandText)
Parameters
connConnectionAndTransactionHolderThe unit of work the statement runs in.
commandTextstringThe statement, with its table prefix already folded in.
Returns
Remarks
This and AddCommandParameter(DbCommand, string, object?) are what a lock handler of your own issues its lock statement through. The accessor behind them stays out of reach — how a command is minted and how a parameter is named differ by driver, and are not a contract — but a subclass that could not prepare a command at all had no way to implement ExecuteSql(Guid, ConnectionAndTransactionHolder, string, string, string, CancellationToken), which is the one method it exists to implement.
ReleaseLock(Guid, SchedulerLock, CancellationToken)
Release the lock on the identified resource if it is held by the calling thread.
public ValueTask ReleaseLock(Guid requestorId, SchedulerLock lockKind, CancellationToken cancellationToken = default)
Parameters
requestorIdGuidlockKindSchedulerLockcancellationTokenCancellationToken