Table of Contents

Class RedisLockHandler

Namespace
Quartz.Extensions.Redis
Assembly
Quartz.Extensions.Redis.dll

A Redis-based ILockHandler that uses distributed locks (SET NX PX) instead of database row locks.

public sealed class RedisLockHandler : ILockHandler
Inheritance
RedisLockHandler
Implements
Inherited Members

Remarks

This lock handler is designed for clustered Quartz.NET setups where jobs are stored in a relational database but lock contention on the QRTZ_LOCKS table causes deadlocks or performance issues under heavy scheduling load.

The implementation uses a two-tier locking strategy: a local SemaphoreSlim prevents redundant Redis round-trips within the same process, and a Redis SET key value NX PX timeout command provides the cross-node distributed lock.

Configure via properties:

quartz.jobStore.lockHandler.type = Quartz.Extensions.Redis.RedisLockHandler, Quartz.Extensions.Redis
quartz.jobStore.lockHandler.redisConfiguration = localhost:6379

Properties

KeyPrefix

Gets or sets the prefix for Redis lock keys.

public string KeyPrefix { get; }

Property Value

string

Remarks

Defaults to "quartz:lock:". The full key format is {KeyPrefix}{SchedulerName}:{lockName}.

LockRetryInterval

Gets or sets the polling interval between SET NX retry attempts.

[TimeSpanParseRule(TimeSpanParseRule.Milliseconds)]
public TimeSpan LockRetryInterval { get; }

Property Value

TimeSpan

Remarks

Defaults to 100 milliseconds.

LockTimeToLive

Gets or sets the lock time-to-live.

[TimeSpanParseRule(TimeSpanParseRule.Milliseconds)]
public TimeSpan LockTimeToLive { get; }

Property Value

TimeSpan

Remarks

Defaults to 30 seconds. The lock automatically expires after this duration, allowing recovery when a node crashes while holding a lock.

RedisConfiguration

Gets or sets the StackExchange.Redis configuration string.

public string RedisConfiguration { get; }

Property Value

string

Remarks

Defaults to "localhost:6379".

RequiresConnection

Whether this lock handler requires a database connection for its lock management operations.

public bool RequiresConnection { get; }

Property Value

bool
See Also

SchedulerName

Gets the scheduler name used to namespace Redis lock keys.

public string? SchedulerName { get; }

Property Value

string

Remarks

Told to the handler by the job store through Initialize(LockHandlerContext).

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.

Initialize(LockHandlerContext)

Called once by the job store before the lock handler is used, telling the handler which scheduler it locks for. The default implementation does nothing, which suits a handler that does not key its locks by scheduler identity.

public void Initialize(LockHandlerContext context)

Parameters

context LockHandlerContext

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

Shutdown(CancellationToken)

Called once by the job store, after the store has released its own resources, telling the handler to release whatever it opened. The default implementation does nothing, which suits a handler that holds nothing of its own.

public ValueTask Shutdown(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

The cancellation instruction.

Returns

ValueTask

Remarks

Named for Shutdown(CancellationToken) rather than for IAsyncDisposable, because it is the store's shutdown that reaches it and a handler is never used again afterwards. It is a default interface member, so a handler written against 4.0's first shape need not implement it — one that holds nothing has nothing to say here.

It runs after the lock this handler guards has stopped being asked for, so an implementation may assume no acquire is in flight. Throwing is not fatal: the store logs it and finishes shutting down, because a scheduler that cannot complete its shutdown is worse than a connection that outlives it.