go / backoff
I use a lookup table for retry backoff.
Pattern
The computed version:
delay := baseDelay
for attempt := range maxAttempts {
err := request(ctx)
if err == nil {
return nil
}
delay *= 2
delay = min(delay, maxDelay)
time.Sleep(delay)
}
The table version:
delays := []time.Duration{
1 * time.Second,
2 * time.Second,
4 * time.Second,
8 * time.Second,
16 * time.Second,
}
for _, delay := range delays {
err := request(ctx)
if err == nil {
return nil
}
time.Sleep(delay)
}
The table version has fewer variables and keeps no state between iterations. A schedule change is one edit.
Example
A retry loop that backs off on 404s from an eventually-consistent API:
var retryDelays = []time.Duration{
1 * time.Second,
2 * time.Second,
4 * time.Second,
8 * time.Second,
16 * time.Second,
}
func fetchFiles(pr int) ([]File, error) {
var files []File
err := api.Get(&files, "pulls/%d/files", pr)
for _, delay := range retryDelays {
if err != ErrNotFound {
break
}
time.Sleep(delay)
err = api.Get(&files, "pulls/%d/files", pr)
}
return files, err
}
Limits
The computed version wins only when the delay sequence is unbounded or the program picks it at runtime.