dotnet/orleans · warning · ThrottlingException

Request rate exceeded, wait {remainingSeconds}s before retry

Error message

Request rate exceeded, wait {remainingSeconds}s before retrying

What it means

Thrown by Voting UserAgentGrain.Throttle when the user's rolling request score exceeds the threshold (ThrottleThreshold = 10). Throttle is called from CreatePoll and AddVote; each call adds 1 to a score that decays over time (DecayRate = ThrottleThreshold / DecayPeriod), so rapid-fire requests accumulate. ThrottlingException carries the seconds to wait before retrying.

Source

Thrown at samples/Voting/Grains/UserAgentGrain.cs:97

        return result;
    }

    private void Throttle()
    {
        // Work out how long it's been since the last call.
        var elapsedSeconds = _stopwatch.Elapsed.TotalSeconds;
        _stopwatch.Restart();

        // Calculate a new score based on a constant rate of score decay and the
        // time which elapsed since the last call.
        _throttleScore = Math.Max(0, _throttleScore - elapsedSeconds * DecayRate) + 1;

        // If the user has exceeded the threshold, deny their request and give them a
        // helpful warning.
        if (_throttleScore > ThrottleThreshold)
        {
            var remainingSeconds = Math.Max(0, (int)Math.Ceiling((_throttleScore - (ThrottleThreshold - 1)) / DecayRate));
            throw new ThrottlingException($"Request rate exceeded, wait {remainingSeconds}s before retrying");
        }
    }
}

View on GitHub (pinned to fca799fa70)

Solutions

  1. Wait the number of seconds in the exception message (remainingSeconds) before retrying.
  2. Space out requests client-side to stay under the decay rate (one request per DecayPeriod/ThrottleThreshold seconds on average).
  3. Catch ThrottlingException specifically and apply exponential backoff.

Example fix

// before
await user.AddVote(pollId, optionId); // throws under burst

// after: honor the retry hint
try { await user.AddVote(pollId, optionId); }
catch (ThrottlingException ex) { await Task.Delay(ParseWait(ex) * 1000); /* retry */ }
Defensive patterns

Strategy: retry

Validate before calling

// Stay under the decay rate: space requests apart
var minInterval = TimeSpan.FromSeconds(DecayPeriod / (double)ThrottleThreshold);
await Task.Delay(minInterval);
await user.AddVote(pollId, optionId);

Type guard

null

Try / catch

async Task AddVoteWithRetry(IUserAgentGrain user, string pollId, int optionId)
{
    while (true)
    {
        try { await user.AddVote(pollId, optionId); return; }
        catch (ThrottlingException ex)
        {
            var seconds = ParseWaitSeconds(ex.Message);
            await Task.Delay(TimeSpan.FromSeconds(seconds));
        }
    }
}

Prevention

When it happens

Trigger: Calling CreatePoll or AddVote more rapidly than the decay allows — e.g. a loop that fires requests with less than DecayPeriod/ThrottleThreshold seconds between them pushes _throttleScore above 10.

Common situations: Load tests or scripts hammering the vote endpoints; a tight client retry loop; many browser tabs voting simultaneously under one user.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/eeee197f292225ee. Report an issue: GitHub.