Retry with exponential backoff and jitter

Go
package main

import (
	"context"
	"fmt"
	"math/rand"
	"time"
)

// Retry calls fn with exponential backoff and jitter until it succeeds or ctx ends.
func Retry(ctx context.Context, attempts int, base time.Duration, fn func(context.Context) error) error {
	var lastErr error
	for attempt := 0; attempt < attempts; attempt++ {
		if attempt > 0 {
			delay := base * time.Duration(1<<uint(attempt-1))
			jitter := time.Duration(rand.Int63n(int64(delay/2) + 1))
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-time.After(delay + jitter):
			}
		}
		if lastErr = fn(ctx); lastErr == nil {
			return nil
		}
	}
	return fmt.Errorf("gave up after %d attempts: %w", attempts, lastErr)
}

func main() {
	calls := 0
	err := Retry(context.Background(), 3, 100*time.Millisecond, func(context.Context) error {
		calls++
		if calls < 3 {
			return fmt.Errorf("temporary failure")
		}
		return nil
	})
	fmt.Println("err:", err, "calls:", calls)
}