Table of Contents

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

tablePrefix string

The table prefix.

schedulerName string

the scheduler name

sql string

The statement that takes the lock.

insertSql string

The statement that inserts the lock row when it does not exist yet.

dbProvider IDbProvider

The 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

string

LockSql

The statement that takes the lock, before the table prefix is folded in.

protected string LockSql { get; }

Property Value

string

RequiresConnection

This lock handler does use the database.

public bool RequiresConnection { get; }

Property Value

bool

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

string

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

string

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

TimeProvider

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

requestorId Guid

Identifies the calling context, so that a re-entrant acquire can be told from a competing one.

conn ConnectionAndTransactionHolder

The 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.

lockKind SchedulerLock

Which of the two locks to take.

cancellationToken CancellationToken

The cancellation instruction.

Returns

ValueTask<bool>

true if this call took the lock and its caller must release it; false if requestorId already 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

cancellationToken fired 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

command DbCommand

The command to bind to.

paramName string

Name of the parameter, without the driver's prefix.

paramValue object

Value to bind; null binds as DBNull.

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

requestorId Guid
conn ConnectionAndTransactionHolder
lockName string
expandedSql string
expandedInsertSql string
cancellationToken CancellationToken

Returns

ValueTask

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

context LockHandlerContext

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

conn ConnectionAndTransactionHolder

The unit of work the statement runs in.

commandText string

The statement, with its table prefix already folded in.

Returns

DbCommand

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

requestorId Guid
lockKind SchedulerLock
cancellationToken CancellationToken

Returns

ValueTask