// Copyright 2019 Sorint.lab // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied // See the License for the specific language governing permissions and // limitations under the License. package util import ( "context" "math/rand" "time" "agola.io/agola/internal/errors" ) // DefaultRetry is the recommended retry for a conflict where multiple clients // are making changes to the same resource. var DefaultRetry = Backoff{ Steps: 5, Duration: 10 * time.Millisecond, Factor: 1.0, Jitter: 0.1, } // DefaultBackoff is the recommended backoff for a conflict where a client // may be attempting to make an unrelated modification to a resource under // active management by one or more controllers. var DefaultBackoff = Backoff{ Steps: 4, Duration: 10 * time.Millisecond, Factor: 5.0, Jitter: 0.1, } // DefaultBackoff is the recommended backoff for a conflict where a client // may be attempting to make an unrelated modification to a resource under // active management by one or more controllers. var FetchFileBackoff = Backoff{ Steps: 4, Duration: 500 * time.Millisecond, Factor: 2.0, Jitter: 0.1, } // Jitter returns a time.Duration between duration and duration + maxFactor * // duration. // // This allows clients to avoid converging on periodic behavior. If maxFactor // is 0.0, a suggested default value will be chosen. func Jitter(duration time.Duration, maxFactor float64) time.Duration { if maxFactor <= 0.0 { maxFactor = 1.0 } wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration)) return wait } // ErrWaitTimeout is returned when the condition exited without success. var ErrWaitTimeout = errors.New("timed out waiting for the condition") // ConditionFunc returns true if the condition is satisfied, or an error // if the loop should be aborted. type ConditionFunc func() (done bool, err error) // Backoff holds parameters applied to a Backoff function. type Backoff struct { Duration time.Duration // the base duration Factor float64 // Duration is multiplied by factor each iteration Jitter float64 // The amount of jitter applied each iteration Steps int // Exit with error after this many steps } // ExponentialBackoff repeats a condition check with exponential backoff. // // It checks the condition up to Steps times, increasing the wait by multiplying // the previous duration by Factor. // // If Jitter is greater than zero, a random amount of each duration is added // (between duration and duration*(1+jitter)). // // If the condition never returns true, ErrWaitTimeout is returned. All other // errors terminate immediately. func ExponentialBackoff(ctx context.Context, backoff Backoff, condition ConditionFunc) error { duration := backoff.Duration for i := 0; i < backoff.Steps; i++ { if i != 0 { adjusted := duration if backoff.Jitter > 0.0 { adjusted = Jitter(duration, backoff.Jitter) } sleepCh := time.NewTimer(adjusted).C select { case <-ctx.Done(): return nil case <-sleepCh: } duration = time.Duration(float64(duration) * backoff.Factor) } if ok, err := condition(); err != nil || ok { return errors.WithStack(err) } } return ErrWaitTimeout }