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
- Wait the number of seconds in the exception message (remainingSeconds) before retrying.
- Space out requests client-side to stay under the decay rate (one request per DecayPeriod/ThrottleThreshold seconds on average).
- 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
- Honor the remainingSeconds hint in the exception before retrying.
- Space requests client-side to stay under the decay rate.
- Use exponential backoff for ThrottlingException specifically.
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
- Invalid option {optionId}
- You have already created 5 polls, which is enough for anybod
- You have already voted in that poll!
- The requested vote option was not found.
- Recovered {name} does not match. Written: {Serialize(written
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/eeee197f292225ee.
Report an issue: GitHub.