microsoft/FASTER · error · ArgumentException

delay

Error message

delay

What it means

LeaseTimer.Schedule validates its delay parameter: it must be non-negative and strictly less than MaxDelay seconds (delay/1000 < MaxDelay). An ArgumentException is thrown when the delay is negative or >= MaxDelay*1000 ms, because such a timer entry cannot be represented/scheduled safely.

Solutions

  1. Clamp the delay into [0, MaxDelay*1000) before calling Schedule.
  2. Check unit conversion: Schedule expects milliseconds; convert seconds values with *1000 and keep under MaxDelay seconds.
  3. Validate any config-sourced interval before passing it in.

Example fix

// before
timer.Schedule(leaseDurationSeconds, callback, token); // wrong unit
// after
int delayMs = Math.Clamp((int)TimeSpan.FromSeconds(leaseDurationSeconds).TotalMilliseconds, 0, (LeaseTimer.MaxDelay * 1000) - 1);
timer.Schedule(delayMs, callback, token);
Defensive patterns

Strategy: validation

Validate before calling

if (delay < 0 || delay >= LeaseTimer.MaxDelay * 1000)
    throw new ArgumentOutOfRangeException(nameof(delay), $"delay must be in [0, {LeaseTimer.MaxDelay * 1000}) ms");

Try / catch

try
{
    await timer.Schedule(delayMs, callback, token);
}
catch (ArgumentException ex) when (ex.ParamName == "delay")
{
    // fix the computed delay and reschedule
}

Prevention

When it happens

Trigger: Calling LeaseTimer.Schedule with delay < 0, or with delay >= MaxDelay*1000 milliseconds (e.g. accidentally passing seconds instead of milliseconds, or computing a lease-renewal delay from a misconfigured value).

Common situations: Unit confusion (seconds vs milliseconds) when computing renewal delays; a config value like lease poll interval parsed as a huge number; arithmetic producing a negative delay from clock skew.

Related errors


AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15). Data as JSON: /api/errors/761f57eb3c9952cf. Report an issue: GitHub.

Appendix: source

Thrown at cs/src/devices/AzureStorageDevice/LeaseTimer.cs:127

            Entry current;
            while (true)
            {
                current = this.schedule[position];
                if (current == null || Interlocked.CompareExchange<Entry>(ref this.schedule[position], null, current) == current)
                {
                    break;
                }
            }

            current?.RunAll();
        }

        public Task Schedule(int delay, Func<Task> callback, CancellationToken token)
        {
            if ((delay / 1000) >= MaxDelay || delay < 0)
            {
                throw new ArgumentException(nameof(delay));
            }

            var entry = new Entry()
            {
               Tcs = new TaskCompletionSource<bool>(),
               Callback = callback,
            };

            entry.Registration = token.Register(entry.Cancel);

            while (true)
            {
                int targetPosition = (this.position + (delay * TicksPerSecond) / 1000) % (MaxDelay * TicksPerSecond);

                if (targetPosition == this.position)
                {
                    return callback();
                }

View on GitHub (pinned to 321d872eab)