Version v1.0 · dotnet

Retry Policy

Retry eligibility, fixed-delay vs backoff behavior, and failure lifecycle.

Retry Policy

When a job throws, DurableStack evaluates retry eligibility using the current attempt and MaxAttempts, then schedules the next try with either fixed delay or backoff.

Retry rule

  • Retry when Attempt < MaxAttempts.
  • Reschedule for UtcNow + calculated delay.
  • Mark terminal failed when max attempts is reached.

MaxAttempts includes the initial attempt. Example: MaxAttempts = 3 allows one initial run + up to two retries.

Retry behavior modes

DurableStack supports two retry modes:

  • FixedDelay (default): every retry uses the same base delay.
  • Backoff: each retry delay grows exponentially from the base delay (baseDelay * 2^(Attempt - 1)).

Attempt is the attempt that just failed. So with backoff and baseDelay = 5s:

  • after attempt 1 fails: 5s
  • after attempt 2 fails: 10s
  • after attempt 3 fails: 20s

Where retry settings come from

Retry mode and initial delay can be set per job via [DurableJob], and global limits/tuning come from AddDurableStack options.

Per-job (attribute)

using DurableStack.Core.Models;
using DurableStack.Hosting.DependencyInjection;

[DurableJob(
    Name = "send-digest-email",
    MaxAttempts = 5,
    RetryBehavior = RetryBehavior.Backoff,
    RetryInitialDelaySeconds = 10)]
public sealed class SendDigestEmailJob : IDurableJob
{
    public Task ExecuteAsync(JobContext context, CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }
}
  • RetryBehavior default: FixedDelay
  • RetryInitialDelaySeconds default: unset (0 on attribute), which falls back to global options.RetryDelay

Global runtime options

builder.Services.AddDurableStack(options =>
{
    options.RetryDelay = TimeSpan.FromSeconds(5); // Default base delay when per-job initial delay is not set.
    options.RetryMaxDelay = TimeSpan.FromHours(1); // Upper cap applied to computed retry delay.
    options.RetryJitterEnabled = false; // Enables random jitter when true.
    options.RetryJitterRatio = 0.2; // +/- jitter ratio (0 to 1) when jitter is enabled.
});

Delay calculation order

  • Start with base delay:
    • per-job RetryInitialDelaySeconds if set and > 0
    • otherwise global options.RetryDelay
  • Apply behavior:
    • FixedDelay: keep base delay
    • Backoff: exponential growth per failed attempt
  • Apply jitter if enabled
  • Clamp to options.RetryMaxDelay if exceeded

Status progression

  • pending -> leased -> succeeded
  • pending -> leased -> pending (retry)
  • pending -> leased -> failed (terminal)

Guidance

  • Use FixedDelay for predictable retry cadence.
  • Use Backoff for transient dependency failures (API throttling, brief outages).
  • Keep job handlers idempotent so retries are safe.
  • Keep MaxAttempts lower for non-idempotent side effects.
  • Monitor retry and failure trends in observability dashboards.