1
0
Fork 0
mirror of https://github.com/imjasonh/gcsbuf synced 2026-07-17 14:23:18 +00:00
gcsbuf/retry.go
Jason Hall f6fd234e2d add test binary, observability
Signed-off-by: Jason Hall <imjasonh@gmail.com>
2026-03-24 14:54:02 -04:00

50 lines
1.2 KiB
Go

package buf
import (
"context"
"errors"
"math/rand/v2"
"net/http"
"time"
"google.golang.org/api/googleapi"
)
const maxRetries = 5
func isRateLimited(err error) bool {
var apiErr *googleapi.Error
return errors.As(err, &apiErr) && apiErr.Code == http.StatusTooManyRequests
}
// retry retries fn on rate-limit (429) errors with exponential backoff and jitter.
// OnOp is fired after each attempt, so callers see every rate-limit hit.
func retry[T any](ctx context.Context, onOp OnOp, op string, fn func() (T, error)) (T, error) {
for attempt := range maxRetries + 1 {
start := time.Now()
v, err := fn()
onOp.observe(op, time.Since(start), err)
if err == nil || !isRateLimited(err) {
return v, err
}
if attempt == maxRetries {
return v, err
}
backoff := min(time.Duration(1<<attempt)*100*time.Millisecond, 5*time.Second)
jitter := time.Duration(rand.Int64N(int64(backoff) / 2))
select {
case <-time.After(backoff + jitter):
case <-ctx.Done():
var zero T
return zero, ctx.Err()
}
}
panic("unreachable")
}
func retryVoid(ctx context.Context, onOp OnOp, op string, fn func() error) error {
_, err := retry(ctx, onOp, op, func() (struct{}, error) {
return struct{}{}, fn()
})
return err
}