Skip to content

fix: coordinate REST API rate limits and bound retries - #618

Open
KAJdev wants to merge 3 commits into
mainfrom
zeke/rest-api-rate-limits
Open

KAJdev wants to merge 3 commits into
mainfrom
zeke/rest-api-rate-limits

Conversation

@KAJdev

@KAJdev KAJdev commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Honor Retry-After and use jittered exponential backoff when no valid hint is available. Retry and wait budgets surface RateLimitError when exhausted.

Comment thread tests/test_api/test_rate_limit.py Fixed
Comment thread runpod/api/_rate_limit.py Fixed
Comment thread tests/test_api/test_rate_limit.py Fixed
Comment thread tests/test_api/test_rate_limit.py Fixed
Comment thread tests/test_api/test_rate_limit.py Fixed
Comment thread runpod/api/_rate_limit.py Fixed
@KAJdev
KAJdev requested a review from deanq October 14, 2026 22:20
@deanq
deanq requested a balanced review from Copilot October 15, 2026 00:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃煛 Changes recommended

Quota reconciliation can incorrectly block available capacity, and retry-delay calculation and reporting need correction.

4 open findings
What changed in this PR

Adds coordinated rate-limit handling to the management REST transport for synchronous and asynchronous callers.

Changes:

  • Shares quota reservations across routes by origin and API key.
  • Adds bounded retries, Retry-After handling, and jittered backoff.
  • Introduces RateLimitError, documentation, and regression tests.
File Description
tests/鈥媡est_api/鈥媡est_rate_limit.py Tests quota coordination, retries, budgets, and cancellation.
runpod/鈥媏rror.py Adds RateLimitError with retry-delay details.
runpod/鈥媋pi/鈥媟est.py Integrates shared quotas and bounded retries.
runpod/鈥媋pi/鈥媉rate_limit.py Implements parsing, reservations, cooldowns, and registry management.
docs/鈥媋pi/鈥媓andling_errors.md Documents rate-limit behavior and configuration.

馃 Review effort: Balanced


馃挕 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/_rate_limit.py

@deanq deanq left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 inline comments below.

Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/_rate_limit.py
Comment thread runpod/api/_rate_limit.py Outdated
Comment on lines +65 to +68
if now >= reset_at:
self.windows[name] = (None, reset_at)
if self.in_flight:
delay = max(delay, 0.05)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a window passes reset_at and remaining is None, every acquire() with something in flight takes the now >= reset_at branch again and returns 0.05. The check doesn't look at whether remaining was already cleared.

Result: after each minute reset, concurrent callers drop to one-at-a-time with 50ms polling until a response refreshes the window (and if the server stops sending that window, until the next update deletes it). Combined with the budget accounting above, this can raise RateLimitError with quota available.

Comment thread runpod/api/rest.py Outdated
Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/rest.py Outdated
Comment thread runpod/api/_rate_limit.py Outdated
if self.in_flight and (
not self.discovered or (self.blocked_until and not delay)
):
delay = 0.05

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting on in-flight requests is a fixed 50ms busy-poll. N waiters each wake 20x/s and take the bucket lock, and the async path takes a threading.Lock on the event loop. A threading.Condition notified from release()/update() (or for async, a computed sleep until the next release) removes the wakeups and up to 50ms of added latency.

Comment thread runpod/api/_rate_limit.py Outdated
Comment thread runpod/api/_rate_limit.py Dismissed

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants