Skip to content

fix: convert backoff delay to seconds when retrying - #962

Closed
frederico-kluser wants to merge 1 commit into
grammyjs:mainfrom
frederico-kluser:fix/retry-backoff-unit-mismatch
Closed

frederico-kluser wants to merge 1 commit into
grammyjs:mainfrom
frederico-kluser:fix/retry-backoff-unit-mismatch

Conversation

@frederico-kluser

Copy link
Copy Markdown

Fixes #961.

The bug

withRetries tracks its backoff in milliseconds — both INITIAL_DELAY and TWENTY_MINUTES say // ms — but passed that value to sleep, which takes seconds. Every backoff wait was 1000× longer than intended: the second retry waited 100 seconds instead of the documented 100 ms, and the ceiling became roughly 13.9 days instead of the documented hour.

That sleep really does take seconds is settled by its neighbours: the 429 branch a few lines above passes the Bot API's retry_after to it directly, and the polling loop passes sleepSeconds. Both are correct, so the conversion belongs at this one call site rather than in sleep.

The change

             if (lastDelay !== INITIAL_DELAY) {
-                await sleep(lastDelay, signal);
+                // `sleep` expects seconds, but the backoff is tracked in
+                // milliseconds, so it has to be converted here
+                await sleep(lastDelay / 1000, signal);
             }

Plus a regression test, and one docstring line.

Why it went unnoticed

The two existing retry tests assert callCount === 2 — the initial attempt plus the first retry. The first retry is deliberately immediate, since if (lastDelay !== INITIAL_DELAY) is false on the first pass, so the suite never reached sleep at all. The delay only appears on the second retry.

The new test drives a second retry under FakeTime and advances the clock by exactly 100 ms. It fails on main (AssertionError: Values are not equal, in 20 ms — it does not hang) and passes with this change.

A question for you

Once the units are fixed, the ceiling is 20 minutes, matching the named constant, while the docstring said "1 hour". I assumed the constant is authoritative and aligned the prose to it. If an hour was the intent, then TWENTY_MINUTES should change instead and I am happy to switch it — just say which.

Checks

deno task dev passes on this branch: deno fmt (59 files), deno lint (45 files), deno task test (39 passed, 449 steps, 0 failed), deno task check. deno fmt left everything but the two edited files untouched.

Reproduction script and measured output are in #961.

`withRetries` tracks its backoff in milliseconds, as both `INITIAL_DELAY` and
`TWENTY_MINUTES` state in their comments, but passed that value to `sleep`,
which takes seconds. Every backoff wait was therefore 1000x longer than
intended: the second retry waited 100 seconds instead of the documented 100 ms,
and the ceiling became about 13.9 days.

That `sleep` takes seconds is confirmed by the 429 branch a few lines above,
which passes the Bot API's `retry_after` to it directly, and by the polling
loop, which passes `sleepSeconds`. So the conversion belongs at this call site.

The failure mode was quiet: during the wait the process stays alive, `start()`
has neither resolved nor rejected, and the retry error only reaches `debugErr`,
which is silent without `DEBUG`. A transient network blip at startup cost at
least 100 seconds of silence.

The existing retry tests assert an initial attempt plus one retry, and the
first retry is deliberately immediate, so no `sleep` was ever reached by the
suite. The new test drives a second retry under a fake clock and fails without
this change.

Also aligns the docstring with the named constant: the ceiling is 20 minutes,
where the text said 1 hour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@KnightNiwrem

Copy link
Copy Markdown
Member

Is there any reason to create this PR when #960 already exist?

To be clear, I'm not saying that we shouldn't have multiple PRs that attempt to solve the same issue. But I would expect them to be at least qualitatively different in the approach they take - at least, to the extent that simply leaving a comment for discussion in the existing PR is insufficient.

@frederico-kluser

Copy link
Copy Markdown
Author

Closing this as a duplicate of #960, which proposes the same fix and was opened three days earlier. I missed it before opening this — my search covered issues but not pull requests, which is entirely on me. Apologies for the noise.

@roshnicdave got there first and their PR should be the one that lands.

I have left the two small points that this PR carried in a comment on #960, and #961 stays open with a runnable reproduction and the impact analysis, in case that is useful when reviewing.

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.

withRetries passes a millisecond backoff to sleep(), which expects seconds (1000x too long)

2 participants