import "pkg:/source/utils/misc.bs"
' Exponential backoff for retrying something that failed: how long to wait before the next
' attempt, and whether a failure is still waiting that long. Pure: callers pass their own clock
' in, so the same failure state works on any thread and in a test.
namespace backoff
' The delay before the retry that follows a 0-based `attempt`: 1 s, 2 s, 4 s, 8 s, 16 s, then
' capped at 30 s.
function nextDelayMs(attempt as integer) as integer
ms = 1000
for i = 1 to attempt
ms = ms * 2
if ms >= 30000 then return 30000
end for
return ms
end function
' The failure state after one more failure.
'
' @param previous - the state afterFailure returned for the last failure, or invalid for the first
' @param nowMs - the caller's clock, in milliseconds
' @return an AA with `attempts` and `retryAt`, the time the next attempt may start on the same clock
function afterFailure(previous as dynamic, nowMs as integer) as object
attempts = 1
if isValid(previous) then attempts = previous.attempts + 1
return { attempts: attempts, retryAt: nowMs + nextDelayMs(attempts - 1) }
end function
' Whether `failure` is still waiting out its delay at `nowMs`. False for invalid: nothing failed.
function isWaiting(failure as dynamic, nowMs as integer) as boolean
return isValid(failure) and failure.retryAt > nowMs
end function
end namespace