package playback import ( "context" "time" ) type attemptFunc func(context.Context) error type retryDecider func(error) bool type waitFunc func(context.Context, time.Duration) error func waitForRetry(ctx context.Context, delay time.Duration) error { timer := time.NewTimer(delay) defer timer.Stop() select { case <-timer.C: return nil case <-ctx.Done(): return ctx.Err() } } func runWithRetry( ctx context.Context, policy RetryPolicy, attempt attemptFunc, shouldRetry retryDecider, wait waitFunc, observer retryObserver, ) error { failedAttempts := 0 for { err := attempt(ctx) if err == nil { return nil } if ctx.Err() != nil { return ctx.Err() } failedAttempts++ willRetry := shouldRetry(err) && policy.canRetry(failedAttempts) if !willRetry { if observer != nil { observer(retryEvent{ FailedAttempts: failedAttempts, Err: err, WillRetry: false, }) } return err } delay := policy.retryDelay(failedAttempts) if observer != nil { observer(retryEvent{ FailedAttempts: failedAttempts, Err: err, RetryIn: delay, WillRetry: true, }) } if err := wait(ctx, delay); err != nil { if ctx.Err() != nil { return ctx.Err() } return err } } } type retryEvent struct { FailedAttempts int Err error RetryIn time.Duration WillRetry bool } type retryObserver func(retryEvent)