package retry import ( "context" "strings" "time" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/utils/clock" ) type IntervalRetryer struct { interval time.Duration doer Doer clock clock.WithTicker } func NewIntervalRetryer(doer Doer, interval time.Duration) *IntervalRetryer { return &IntervalRetryer{ interval: interval, doer: doer, clock: clock.RealClock{}, } } func (r *IntervalRetryer) Do(ctx context.Context) error { ticker := r.clock.NewTicker(r.interval) defer ticker.Stop() for { err := r.doer.Do(ctx) if err == nil { return nil } if !r.serviceIsUnavailable(err) { return err } select { case <-ctx.Done(): // TODO(katexochen): is this necessary? return ctx.Err() case <-ticker.C(): } } } func (r *IntervalRetryer) serviceIsUnavailable(err error) bool { statusErr, ok := status.FromError(err) if !ok { return false } if statusErr.Code() != codes.Unavailable { return false } // ideally we would check the error type directly, but grpc only provides a string return strings.HasPrefix(statusErr.Message(), `connection error: desc = "transport: authentication handshake failed`) } type Doer interface { Do(ctx context.Context) error }