Skip to content

Port: Handle transient database exceptions with automatic retry (3.x)#2883

Merged
lahma merged 1 commit into
3.xfrom
port/transient-retry-3x
Mar 8, 2026
Merged

Port: Handle transient database exceptions with automatic retry (3.x)#2883
lahma merged 1 commit into
3.xfrom
port/transient-retry-3x

Conversation

@lahma

@lahma lahma commented Mar 8, 2026

Copy link
Copy Markdown
Member

Summary

Test plan

  • All 4 new unit tests pass (retry-succeeds, retry-exhausted, non-transient-no-retry, disabled-retry)
  • Existing JobStoreSupportTest still passes
  • Builds successfully on net10.0
  • Verify CI passes

🤖 Generated with Claude Code

Port of #2863: adds automatic retry for transient database exceptions
(e.g. SQL Server deadlocks) in ExecuteInNonManagedTXLock with separate
TransientRetryInterval (default 1s) and MaxTransientRetries (default 3).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@sonarqubecloud

sonarqubecloud Bot commented Mar 8, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ports the transient database exception retry behavior for ADO.NET JobStore from mainline into the 3.x branch, aiming to reduce startup failures (e.g., SQL Server deadlocks) by retrying ExecuteInNonManagedTXLock operations.

Changes:

  • Added MaxTransientRetries and TransientRetryInterval configuration knobs (defaults: 3 retries, 1s delay) to JobStoreSupport.
  • Updated ExecuteInNonManagedTXLock to retry on exceptions detected as transient via IsTransient().
  • Exposed the new knobs through SchedulerBuilder.PersistentStoreOptions and added unit tests covering the retry behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/Quartz/SchedulerBuilder.cs Exposes persistent store configuration for transient retry count and delay.
src/Quartz/Impl/AdoJobStore/JobStoreSupport.cs Implements transient retry loop and adds new configurable properties with defaults.
src/Quartz.Tests.Unit/Impl/AdoJobStore/JobStoreSupportTest.cs Adds unit tests verifying transient retry success/failure/disable scenarios.

}

// Delay before the next attempt
await Task.Delay(TransientRetryInterval, cancellationToken).ConfigureAwait(false);

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Task.Delay(TransientRetryInterval, ...) will throw ArgumentOutOfRangeException if TransientRetryInterval is configured negative. Since TransientRetryInterval is publicly settable via config and SchedulerBuilder, it’s worth validating it (>= TimeSpan.Zero) before use (e.g., in Initialize) and/or guarding the delay (only delay when > TimeSpan.Zero).

Suggested change
await Task.Delay(TransientRetryInterval, cancellationToken).ConfigureAwait(false);
if (TransientRetryInterval > TimeSpan.Zero)
{
await Task.Delay(TransientRetryInterval, cancellationToken).ConfigureAwait(false);
}

Copilot uses AI. Check for mistakes.
ConnectionAndTransactionHolder? conn = null;
try
int maxRetries = MaxTransientRetries;
int totalAttempts = maxRetries + 1;

Copilot AI Mar 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MaxTransientRetries is used directly to compute totalAttempts = maxRetries + 1 without any validation. If configured to a negative value (or very large value causing overflow), the retry loop can be skipped and the method will throw InvalidOperationException("...loop exited unexpectedly"), which is confusing and unrelated to the original DB error. Consider validating MaxTransientRetries >= 0 during initialization (fail fast with a config exception) and/or clamping to 0, and compute totalAttempts in a checked context to avoid overflow.

Suggested change
int totalAttempts = maxRetries + 1;
if (maxRetries < 0)
{
maxRetries = 0;
}
int totalAttempts;
try
{
totalAttempts = checked(maxRetries + 1);
}
catch (OverflowException)
{
totalAttempts = int.MaxValue;
}

Copilot uses AI. Check for mistakes.
@lahma
lahma merged commit 6e70022 into 3.x Mar 8, 2026
12 of 13 checks passed
@lahma
lahma deleted the port/transient-retry-3x branch March 8, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants