fix(rate_limit): don't block the executor thread while waiting to retry

Merged
PC
1d ago
+55-2

send_with_retry_opts (and retry! / retry_opts!) waited between retries with std::thread::sleep inside an async fn. On a single-threaded runtime (or whenever worker threads are saturated) that stalls every other task for the whole backoff, which is at least 1 s by default and ratelimit_reset seconds when the API sends it.

Fix. Wait with futures_timer::Delay, which is runtime-agnostic (the crate does not depend on tokio outside dev-dependencies) and is already in the dependency tree through governor. Added a CHANGELOG entry under Unreleased.

Test. rate_limit::test::test_retry_does_not_block_executor runs the retry next to a ticker task on a current_thread runtime and asserts the ticker made progress before the retry fired (red before: the ticker never ran during the sleep).

Reproduction

retry_opts! on a call that returns Error::RateLimit, joined with any other task on #[tokio::main(flavor = "current_thread")]: the other task does not run until the backoff ends.


This PR was prepared with the assistance of an AI tool (Claude Code).


Summary by cubic

Stops send_with_retry_opts (and the retry!/retry_opts! macros) from blocking the executor thread between retries. Previously the backoff used std::thread::sleep in an async context, which stalled every other task on single-threaded runtimes for the full wait duration. It now awaits futures_timer::Delay instead, which is runtime-agnostic and already in the dependency tree via governor.

Tests

  • Adds test_retry_does_not_block_executor, which runs a retry alongside a ticker task on a current_thread runtime and asserts the ticker makes progress while the retry waits.