HangfireIO/Hangfire · error · ArgumentOutOfRangeException
Timeout value should be equal to or greater than zero.
Error message
Timeout value should be equal to or greater than zero.
What it means
ArgumentOutOfRangeException thrown by the ExpirationManager constructor when stateExpirationTimeout is less than TimeSpan.Zero. The background service that removes expired Hangfire records requires a non-negative grace window; a negative value is treated as a programming/config error. (Note: checkInterval has a stricter rule and must be strictly greater than zero.)
Source
Thrown at src/Hangfire.SqlServer/ExpirationManager.cs:59
private static readonly string[] ProcessedTables =
{
"AggregatedCounter",
"Job",
"List",
"Set",
"Hash",
};
private readonly ILog _logger = LogProvider.For<ExpirationManager>();
private readonly SqlServerStorage _storage;
private readonly TimeSpan _stateExpirationTimeout;
private readonly TimeSpan _checkInterval;
public ExpirationManager(SqlServerStorage storage, TimeSpan stateExpirationTimeout, TimeSpan checkInterval)
{
if (storage == null) throw new ArgumentNullException(nameof(storage));
if (stateExpirationTimeout < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(stateExpirationTimeout), "Timeout value should be equal to or greater than zero.");
if (checkInterval <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(checkInterval), "Timeout value should be greater than zero.");
_storage = storage;
_stateExpirationTimeout = stateExpirationTimeout;
_checkInterval = checkInterval;
}
public void Execute(CancellationToken cancellationToken)
{
ExecuteCore(_storage, cancellationToken);
}
public void Execute(BackgroundProcessContext context)
{
if (context.Storage is not SqlServerStorage storage)
{
return;
}View on GitHub (pinned to c236dd0f93)
Solutions
- Clamp the stateExpirationTimeout to TimeSpan.Zero (or a sensible positive value) before constructing ExpirationManager / SqlServerStorageOptions.
- Validate config values at startup and throw a domain-specific message if negative.
- Use TimeSpan.Max(TimeSpan.Zero, computed) when deriving the timeout arithmetically.
Example fix
// before var mgr = new ExpirationManager(storage, deadline - now, checkInterval); // after var mgr = new ExpirationManager(storage, TimeSpan.Max(TimeSpan.Zero, deadline - now), checkInterval);
Defensive patterns
Strategy: validation
Validate before calling
static TimeSpan AssertNonNegative(TimeSpan value, string name)
{
if (value < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(name, "Value must be >= TimeSpan.Zero.");
return value;
} Prevention
- Validate time-derived config values at startup rather than letting them reach the constructor.
- Use TimeSpan.Max(TimeSpan.Zero, computed) for arithmetic results.
- Unit-test ExpirationManager construction with boundary values (zero, negative).
When it happens
Trigger: Instantiating ExpirationManager (directly or via SqlServerStorage options that flow into it) with a negative stateExpirationTimeout TimeSpan.
Common situations: Computing the timeout as a difference that went negative (e.g. deadline - now when deadline already passed); reading a negative value from configuration; passing TimeSpan.FromTicks(-1) accidentally.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- At least one unstarted thread should be created.
- Multiple provider instances registered for queues: {String.J
- The value must be a positive number
- The `timeOut` value must be positive.
- Attempts value must be equal or greater than zero.
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/3d9207c9e168d5a3.
Report an issue: GitHub.