{"record":{"id":"eeee197f292225ee","repo":"dotnet/orleans","slug":"request-rate-exceeded-wait-remainingseconds-s-be","errorCode":null,"errorMessage":"Request rate exceeded, wait {remainingSeconds}s before retrying","messagePattern":"Request rate exceeded, wait (.+?)s before retrying","errorType":"exception","errorClass":"ThrottlingException","httpStatus":null,"severity":"warning","filePath":"samples/Voting/Grains/UserAgentGrain.cs","lineNumber":97,"sourceCode":"        return result;\n    }\n\n    private void Throttle()\n    {\n        // Work out how long it's been since the last call.\n        var elapsedSeconds = _stopwatch.Elapsed.TotalSeconds;\n        _stopwatch.Restart();\n\n        // Calculate a new score based on a constant rate of score decay and the\n        // time which elapsed since the last call.\n        _throttleScore = Math.Max(0, _throttleScore - elapsedSeconds * DecayRate) + 1;\n\n        // If the user has exceeded the threshold, deny their request and give them a\n        // helpful warning.\n        if (_throttleScore > ThrottleThreshold)\n        {\n            var remainingSeconds = Math.Max(0, (int)Math.Ceiling((_throttleScore - (ThrottleThreshold - 1)) / DecayRate));\n            throw new ThrottlingException($\"Request rate exceeded, wait {remainingSeconds}s before retrying\");\n        }\n    }\n}\n","sourceCodeStart":79,"sourceCodeEnd":101,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/samples/Voting/Grains/UserAgentGrain.cs#L79-L101","documentation":"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.","triggerScenarios":"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.","commonSituations":"Load tests or scripts hammering the vote endpoints; a tight client retry loop; many browser tabs voting simultaneously under one user.","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."],"exampleFix":"// before\nawait user.AddVote(pollId, optionId); // throws under burst\n\n// after: honor the retry hint\ntry { await user.AddVote(pollId, optionId); }\ncatch (ThrottlingException ex) { await Task.Delay(ParseWait(ex) * 1000); /* retry */ }","handlingStrategy":"retry","validationCode":"// Stay under the decay rate: space requests apart\nvar minInterval = TimeSpan.FromSeconds(DecayPeriod / (double)ThrottleThreshold);\nawait Task.Delay(minInterval);\nawait user.AddVote(pollId, optionId);","typeGuard":"null","tryCatchPattern":"async Task AddVoteWithRetry(IUserAgentGrain user, string pollId, int optionId)\n{\n    while (true)\n    {\n        try { await user.AddVote(pollId, optionId); return; }\n        catch (ThrottlingException ex)\n        {\n            var seconds = ParseWaitSeconds(ex.Message);\n            await Task.Delay(TimeSpan.FromSeconds(seconds));\n        }\n    }\n}","preventionTips":["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."],"tags":["voting","throttling","rate-limit","sample"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}