// Package retry contains a simple retry mechanism defined by a slice of delay // times. There are no maximum retries accounted for here. If retries should be // limited, use a Timeout context to keep from retrying forever. This should // probably be made into something more robust. package retry import ( "context" "time" ) // queryPollIntervals is a slice of the delays before re-checking the status on // an executing query, backing off from a short delay at first. This sequence // has been selected with Athena queries in mind, which may operate very // quickly for things like schema manipulation, or which may run for an // extended period of time, when running an actual data analysis query. // Long-running queries will exhaust their rapid retries quickly, and fall back // to checking every few seconds or longer. var DefaultPollIntervals = []time.Duration{ time.Millisecond, 2 * time.Millisecond, 2 * time.Millisecond, 5 * time.Millisecond, 10 * time.Millisecond, 20 * time.Millisecond, 50 * time.Millisecond, 50 * time.Millisecond, 100 * time.Millisecond, 100 * time.Millisecond, 200 * time.Millisecond, 500 * time.Millisecond, time.Second, 2 * time.Second, 5 * time.Second, 10 * time.Second, 20 * time.Second, 30 * time.Second, time.Minute, } // delayer keeps track of the current delay between retries. type delayer struct { Delays []time.Duration currentIndex int } // Delay returns the current delay duration, and advances the index to the next // delay defined. If the index has reached the end of the delay slice, then it // will continue to return the maximum delay defined. func (d *delayer) Delay() time.Duration { t := d.Delays[d.currentIndex] if d.currentIndex < len(d.Delays)-1 { d.currentIndex++ } return t } // Retry uses a slice of time.Duration interval delays to retry a function // until it either errors or indicates that it is ready to proceed. If f // returns true, or an error, the retry loop is broken. Pass a closure as f if // you need to record a value from the operation that you are performing inside // f. func Retry(ctx context.Context, retryIntervals []time.Duration, f func() (bool, error)) (err error) { if retryIntervals == nil || len(retryIntervals) == 0 { retryIntervals = DefaultPollIntervals } d := delayer{Delays: retryIntervals} for { select { case <-ctx.Done(): return ctx.Err() default: ok, err := f() if err != nil { return err } if ok { return nil } time.Sleep(d.Delay()) } } return err }