mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
rebase: update all k8s packages to 0.27.2
Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
committed by
mergify[bot]
parent
07b05616a0
commit
2551a0b05f
502
vendor/k8s.io/apimachinery/pkg/util/wait/backoff.go
generated
vendored
Normal file
502
vendor/k8s.io/apimachinery/pkg/util/wait/backoff.go
generated
vendored
Normal file
@ -0,0 +1,502 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 wait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// Backoff holds parameters applied to a Backoff function.
|
||||
type Backoff struct {
|
||||
// The initial duration.
|
||||
Duration time.Duration
|
||||
// Duration is multiplied by factor each iteration, if factor is not zero
|
||||
// and the limits imposed by Steps and Cap have not been reached.
|
||||
// Should not be negative.
|
||||
// The jitter does not contribute to the updates to the duration parameter.
|
||||
Factor float64
|
||||
// The sleep at each iteration is the duration plus an additional
|
||||
// amount chosen uniformly at random from the interval between
|
||||
// zero and `jitter*duration`.
|
||||
Jitter float64
|
||||
// The remaining number of iterations in which the duration
|
||||
// parameter may change (but progress can be stopped earlier by
|
||||
// hitting the cap). If not positive, the duration is not
|
||||
// changed. Used for exponential backoff in combination with
|
||||
// Factor and Cap.
|
||||
Steps int
|
||||
// A limit on revised values of the duration parameter. If a
|
||||
// multiplication by the factor parameter would make the duration
|
||||
// exceed the cap then the duration is set to the cap and the
|
||||
// steps parameter is set to zero.
|
||||
Cap time.Duration
|
||||
}
|
||||
|
||||
// Step returns an amount of time to sleep determined by the original
|
||||
// Duration and Jitter. The backoff is mutated to update its Steps and
|
||||
// Duration. A nil Backoff always has a zero-duration step.
|
||||
func (b *Backoff) Step() time.Duration {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
var nextDuration time.Duration
|
||||
nextDuration, b.Duration, b.Steps = delay(b.Steps, b.Duration, b.Cap, b.Factor, b.Jitter)
|
||||
return nextDuration
|
||||
}
|
||||
|
||||
// DelayFunc returns a function that will compute the next interval to
|
||||
// wait given the arguments in b. It does not mutate the original backoff
|
||||
// but the function is safe to use only from a single goroutine.
|
||||
func (b Backoff) DelayFunc() DelayFunc {
|
||||
steps := b.Steps
|
||||
duration := b.Duration
|
||||
cap := b.Cap
|
||||
factor := b.Factor
|
||||
jitter := b.Jitter
|
||||
|
||||
return func() time.Duration {
|
||||
var nextDuration time.Duration
|
||||
// jitter is applied per step and is not cumulative over multiple steps
|
||||
nextDuration, duration, steps = delay(steps, duration, cap, factor, jitter)
|
||||
return nextDuration
|
||||
}
|
||||
}
|
||||
|
||||
// Timer returns a timer implementation appropriate to this backoff's parameters
|
||||
// for use with wait functions.
|
||||
func (b Backoff) Timer() Timer {
|
||||
if b.Steps > 1 || b.Jitter != 0 {
|
||||
return &variableTimer{new: internalClock.NewTimer, fn: b.DelayFunc()}
|
||||
}
|
||||
if b.Duration > 0 {
|
||||
return &fixedTimer{new: internalClock.NewTicker, interval: b.Duration}
|
||||
}
|
||||
return newNoopTimer()
|
||||
}
|
||||
|
||||
// delay implements the core delay algorithm used in this package.
|
||||
func delay(steps int, duration, cap time.Duration, factor, jitter float64) (_ time.Duration, next time.Duration, nextSteps int) {
|
||||
// when steps is non-positive, do not alter the base duration
|
||||
if steps < 1 {
|
||||
if jitter > 0 {
|
||||
return Jitter(duration, jitter), duration, 0
|
||||
}
|
||||
return duration, duration, 0
|
||||
}
|
||||
steps--
|
||||
|
||||
// calculate the next step's interval
|
||||
if factor != 0 {
|
||||
next = time.Duration(float64(duration) * factor)
|
||||
if cap > 0 && next > cap {
|
||||
next = cap
|
||||
steps = 0
|
||||
}
|
||||
} else {
|
||||
next = duration
|
||||
}
|
||||
|
||||
// add jitter for this step
|
||||
if jitter > 0 {
|
||||
duration = Jitter(duration, jitter)
|
||||
}
|
||||
|
||||
return duration, next, steps
|
||||
|
||||
}
|
||||
|
||||
// DelayWithReset returns a DelayFunc that will return the appropriate next interval to
|
||||
// wait. Every resetInterval the backoff parameters are reset to their initial state.
|
||||
// This method is safe to invoke from multiple goroutines, but all calls will advance
|
||||
// the backoff state when Factor is set. If Factor is zero, this method is the same as
|
||||
// invoking b.DelayFunc() since Steps has no impact without Factor. If resetInterval is
|
||||
// zero no backoff will be performed as the same calling DelayFunc with a zero factor
|
||||
// and steps.
|
||||
func (b Backoff) DelayWithReset(c clock.Clock, resetInterval time.Duration) DelayFunc {
|
||||
if b.Factor <= 0 {
|
||||
return b.DelayFunc()
|
||||
}
|
||||
if resetInterval <= 0 {
|
||||
b.Steps = 0
|
||||
b.Factor = 0
|
||||
return b.DelayFunc()
|
||||
}
|
||||
return (&backoffManager{
|
||||
backoff: b,
|
||||
initialBackoff: b,
|
||||
resetInterval: resetInterval,
|
||||
|
||||
clock: c,
|
||||
lastStart: c.Now(),
|
||||
timer: nil,
|
||||
}).Step
|
||||
}
|
||||
|
||||
// Until loops until stop channel is closed, running f every period.
|
||||
//
|
||||
// Until is syntactic sugar on top of JitterUntil with zero jitter factor and
|
||||
// with sliding = true (which means the timer for period starts after the f
|
||||
// completes).
|
||||
func Until(f func(), period time.Duration, stopCh <-chan struct{}) {
|
||||
JitterUntil(f, period, 0.0, true, stopCh)
|
||||
}
|
||||
|
||||
// UntilWithContext loops until context is done, running f every period.
|
||||
//
|
||||
// UntilWithContext is syntactic sugar on top of JitterUntilWithContext
|
||||
// with zero jitter factor and with sliding = true (which means the timer
|
||||
// for period starts after the f completes).
|
||||
func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) {
|
||||
JitterUntilWithContext(ctx, f, period, 0.0, true)
|
||||
}
|
||||
|
||||
// NonSlidingUntil loops until stop channel is closed, running f every
|
||||
// period.
|
||||
//
|
||||
// NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter
|
||||
// factor, with sliding = false (meaning the timer for period starts at the same
|
||||
// time as the function starts).
|
||||
func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) {
|
||||
JitterUntil(f, period, 0.0, false, stopCh)
|
||||
}
|
||||
|
||||
// NonSlidingUntilWithContext loops until context is done, running f every
|
||||
// period.
|
||||
//
|
||||
// NonSlidingUntilWithContext is syntactic sugar on top of JitterUntilWithContext
|
||||
// with zero jitter factor, with sliding = false (meaning the timer for period
|
||||
// starts at the same time as the function starts).
|
||||
func NonSlidingUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) {
|
||||
JitterUntilWithContext(ctx, f, period, 0.0, false)
|
||||
}
|
||||
|
||||
// JitterUntil loops until stop channel is closed, running f every period.
|
||||
//
|
||||
// If jitterFactor is positive, the period is jittered before every run of f.
|
||||
// If jitterFactor is not positive, the period is unchanged and not jittered.
|
||||
//
|
||||
// If sliding is true, the period is computed after f runs. If it is false then
|
||||
// period includes the runtime for f.
|
||||
//
|
||||
// Close stopCh to stop. f may not be invoked if stop channel is already
|
||||
// closed. Pass NeverStop to if you don't want it stop.
|
||||
func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) {
|
||||
BackoffUntil(f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding, stopCh)
|
||||
}
|
||||
|
||||
// BackoffUntil loops until stop channel is closed, run f every duration given by BackoffManager.
|
||||
//
|
||||
// If sliding is true, the period is computed after f runs. If it is false then
|
||||
// period includes the runtime for f.
|
||||
func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan struct{}) {
|
||||
var t clock.Timer
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if !sliding {
|
||||
t = backoff.Backoff()
|
||||
}
|
||||
|
||||
func() {
|
||||
defer runtime.HandleCrash()
|
||||
f()
|
||||
}()
|
||||
|
||||
if sliding {
|
||||
t = backoff.Backoff()
|
||||
}
|
||||
|
||||
// NOTE: b/c there is no priority selection in golang
|
||||
// it is possible for this to race, meaning we could
|
||||
// trigger t.C and stopCh, and t.C select falls through.
|
||||
// In order to mitigate we re-check stopCh at the beginning
|
||||
// of every loop to prevent extra executions of f().
|
||||
select {
|
||||
case <-stopCh:
|
||||
if !t.Stop() {
|
||||
<-t.C()
|
||||
}
|
||||
return
|
||||
case <-t.C():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JitterUntilWithContext loops until context is done, running f every period.
|
||||
//
|
||||
// If jitterFactor is positive, the period is jittered before every run of f.
|
||||
// If jitterFactor is not positive, the period is unchanged and not jittered.
|
||||
//
|
||||
// If sliding is true, the period is computed after f runs. If it is false then
|
||||
// period includes the runtime for f.
|
||||
//
|
||||
// Cancel context to stop. f may not be invoked if context is already expired.
|
||||
func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) {
|
||||
JitterUntil(func() { f(ctx) }, period, jitterFactor, sliding, ctx.Done())
|
||||
}
|
||||
|
||||
// backoffManager provides simple backoff behavior in a threadsafe manner to a caller.
|
||||
type backoffManager struct {
|
||||
backoff Backoff
|
||||
initialBackoff Backoff
|
||||
resetInterval time.Duration
|
||||
|
||||
clock clock.Clock
|
||||
|
||||
lock sync.Mutex
|
||||
lastStart time.Time
|
||||
timer clock.Timer
|
||||
}
|
||||
|
||||
// Step returns the expected next duration to wait.
|
||||
func (b *backoffManager) Step() time.Duration {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
switch {
|
||||
case b.resetInterval == 0:
|
||||
b.backoff = b.initialBackoff
|
||||
case b.clock.Now().Sub(b.lastStart) > b.resetInterval:
|
||||
b.backoff = b.initialBackoff
|
||||
b.lastStart = b.clock.Now()
|
||||
}
|
||||
return b.backoff.Step()
|
||||
}
|
||||
|
||||
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer
|
||||
// for exponential backoff. The returned timer must be drained before calling Backoff() the second
|
||||
// time.
|
||||
func (b *backoffManager) Backoff() clock.Timer {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.timer == nil {
|
||||
b.timer = b.clock.NewTimer(b.Step())
|
||||
} else {
|
||||
b.timer.Reset(b.Step())
|
||||
}
|
||||
return b.timer
|
||||
}
|
||||
|
||||
// Timer returns a new Timer instance that shares the clock and the reset behavior with all other
|
||||
// timers.
|
||||
func (b *backoffManager) Timer() Timer {
|
||||
return DelayFunc(b.Step).Timer(b.clock)
|
||||
}
|
||||
|
||||
// BackoffManager manages backoff with a particular scheme based on its underlying implementation.
|
||||
type BackoffManager interface {
|
||||
// Backoff returns a shared clock.Timer that is Reset on every invocation. This method is not
|
||||
// safe for use from multiple threads. It returns a timer for backoff, and caller shall backoff
|
||||
// until Timer.C() drains. If the second Backoff() is called before the timer from the first
|
||||
// Backoff() call finishes, the first timer will NOT be drained and result in undetermined
|
||||
// behavior.
|
||||
Backoff() clock.Timer
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed when the legacy polling functions are removed.
|
||||
type exponentialBackoffManagerImpl struct {
|
||||
backoff *Backoff
|
||||
backoffTimer clock.Timer
|
||||
lastBackoffStart time.Time
|
||||
initialBackoff time.Duration
|
||||
backoffResetDuration time.Duration
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// NewExponentialBackoffManager returns a manager for managing exponential backoff. Each backoff is jittered and
|
||||
// backoff will not exceed the given max. If the backoff is not called within resetDuration, the backoff is reset.
|
||||
// This backoff manager is used to reduce load during upstream unhealthiness.
|
||||
//
|
||||
// Deprecated: Will be removed when the legacy Poll methods are removed. Callers should construct a
|
||||
// Backoff struct, use DelayWithReset() to get a DelayFunc that periodically resets itself, and then
|
||||
// invoke Timer() when calling wait.BackoffUntil.
|
||||
//
|
||||
// Instead of:
|
||||
//
|
||||
// bm := wait.NewExponentialBackoffManager(init, max, reset, factor, jitter, clock)
|
||||
// ...
|
||||
// wait.BackoffUntil(..., bm.Backoff, ...)
|
||||
//
|
||||
// Use:
|
||||
//
|
||||
// delayFn := wait.Backoff{
|
||||
// Duration: init,
|
||||
// Cap: max,
|
||||
// Steps: int(math.Ceil(float64(max) / float64(init))), // now a required argument
|
||||
// Factor: factor,
|
||||
// Jitter: jitter,
|
||||
// }.DelayWithReset(reset, clock)
|
||||
// wait.BackoffUntil(..., delayFn.Timer(), ...)
|
||||
func NewExponentialBackoffManager(initBackoff, maxBackoff, resetDuration time.Duration, backoffFactor, jitter float64, c clock.Clock) BackoffManager {
|
||||
return &exponentialBackoffManagerImpl{
|
||||
backoff: &Backoff{
|
||||
Duration: initBackoff,
|
||||
Factor: backoffFactor,
|
||||
Jitter: jitter,
|
||||
|
||||
// the current impl of wait.Backoff returns Backoff.Duration once steps are used up, which is not
|
||||
// what we ideally need here, we set it to max int and assume we will never use up the steps
|
||||
Steps: math.MaxInt32,
|
||||
Cap: maxBackoff,
|
||||
},
|
||||
backoffTimer: nil,
|
||||
initialBackoff: initBackoff,
|
||||
lastBackoffStart: c.Now(),
|
||||
backoffResetDuration: resetDuration,
|
||||
clock: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *exponentialBackoffManagerImpl) getNextBackoff() time.Duration {
|
||||
if b.clock.Now().Sub(b.lastBackoffStart) > b.backoffResetDuration {
|
||||
b.backoff.Steps = math.MaxInt32
|
||||
b.backoff.Duration = b.initialBackoff
|
||||
}
|
||||
b.lastBackoffStart = b.clock.Now()
|
||||
return b.backoff.Step()
|
||||
}
|
||||
|
||||
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for exponential backoff.
|
||||
// The returned timer must be drained before calling Backoff() the second time
|
||||
func (b *exponentialBackoffManagerImpl) Backoff() clock.Timer {
|
||||
if b.backoffTimer == nil {
|
||||
b.backoffTimer = b.clock.NewTimer(b.getNextBackoff())
|
||||
} else {
|
||||
b.backoffTimer.Reset(b.getNextBackoff())
|
||||
}
|
||||
return b.backoffTimer
|
||||
}
|
||||
|
||||
// Deprecated: Will be removed when the legacy polling functions are removed.
|
||||
type jitteredBackoffManagerImpl struct {
|
||||
clock clock.Clock
|
||||
duration time.Duration
|
||||
jitter float64
|
||||
backoffTimer clock.Timer
|
||||
}
|
||||
|
||||
// NewJitteredBackoffManager returns a BackoffManager that backoffs with given duration plus given jitter. If the jitter
|
||||
// is negative, backoff will not be jittered.
|
||||
//
|
||||
// Deprecated: Will be removed when the legacy Poll methods are removed. Callers should construct a
|
||||
// Backoff struct and invoke Timer() when calling wait.BackoffUntil.
|
||||
//
|
||||
// Instead of:
|
||||
//
|
||||
// bm := wait.NewJitteredBackoffManager(duration, jitter, clock)
|
||||
// ...
|
||||
// wait.BackoffUntil(..., bm.Backoff, ...)
|
||||
//
|
||||
// Use:
|
||||
//
|
||||
// wait.BackoffUntil(..., wait.Backoff{Duration: duration, Jitter: jitter}.Timer(), ...)
|
||||
func NewJitteredBackoffManager(duration time.Duration, jitter float64, c clock.Clock) BackoffManager {
|
||||
return &jitteredBackoffManagerImpl{
|
||||
clock: c,
|
||||
duration: duration,
|
||||
jitter: jitter,
|
||||
backoffTimer: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jitteredBackoffManagerImpl) getNextBackoff() time.Duration {
|
||||
jitteredPeriod := j.duration
|
||||
if j.jitter > 0.0 {
|
||||
jitteredPeriod = Jitter(j.duration, j.jitter)
|
||||
}
|
||||
return jitteredPeriod
|
||||
}
|
||||
|
||||
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for jittered backoff.
|
||||
// The returned timer must be drained before calling Backoff() the second time
|
||||
func (j *jitteredBackoffManagerImpl) Backoff() clock.Timer {
|
||||
backoff := j.getNextBackoff()
|
||||
if j.backoffTimer == nil {
|
||||
j.backoffTimer = j.clock.NewTimer(backoff)
|
||||
} else {
|
||||
j.backoffTimer.Reset(backoff)
|
||||
}
|
||||
return j.backoffTimer
|
||||
}
|
||||
|
||||
// ExponentialBackoff repeats a condition check with exponential backoff.
|
||||
//
|
||||
// It repeatedly checks the condition and then sleeps, using `backoff.Step()`
|
||||
// to determine the length of the sleep and adjust Duration and Steps.
|
||||
// Stops and returns as soon as:
|
||||
// 1. the condition check returns true or an error,
|
||||
// 2. `backoff.Steps` checks of the condition have been done, or
|
||||
// 3. a sleep truncated by the cap on duration has been completed.
|
||||
// In case (1) the returned error is what the condition function returned.
|
||||
// In all other cases, ErrWaitTimeout is returned.
|
||||
//
|
||||
// Since backoffs are often subject to cancellation, we recommend using
|
||||
// ExponentialBackoffWithContext and passing a context to the method.
|
||||
func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error {
|
||||
for backoff.Steps > 0 {
|
||||
if ok, err := runConditionWithCrashProtection(condition); err != nil || ok {
|
||||
return err
|
||||
}
|
||||
if backoff.Steps == 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(backoff.Step())
|
||||
}
|
||||
return ErrWaitTimeout
|
||||
}
|
||||
|
||||
// ExponentialBackoffWithContext repeats a condition check with exponential backoff.
|
||||
// It immediately returns an error if the condition returns an error, the context is cancelled
|
||||
// or hits the deadline, or if the maximum attempts defined in backoff is exceeded (ErrWaitTimeout).
|
||||
// If an error is returned by the condition the backoff stops immediately. The condition will
|
||||
// never be invoked more than backoff.Steps times.
|
||||
func ExponentialBackoffWithContext(ctx context.Context, backoff Backoff, condition ConditionWithContextFunc) error {
|
||||
for backoff.Steps > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if ok, err := runConditionWithCrashProtectionWithContext(ctx, condition); err != nil || ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if backoff.Steps == 1 {
|
||||
break
|
||||
}
|
||||
|
||||
waitBeforeRetry := backoff.Step()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(waitBeforeRetry):
|
||||
}
|
||||
}
|
||||
|
||||
return ErrWaitTimeout
|
||||
}
|
51
vendor/k8s.io/apimachinery/pkg/util/wait/delay.go
generated
vendored
Normal file
51
vendor/k8s.io/apimachinery/pkg/util/wait/delay.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 wait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// DelayFunc returns the next time interval to wait.
|
||||
type DelayFunc func() time.Duration
|
||||
|
||||
// Timer takes an arbitrary delay function and returns a timer that can handle arbitrary interval changes.
|
||||
// Use Backoff{...}.Timer() for simple delays and more efficient timers.
|
||||
func (fn DelayFunc) Timer(c clock.Clock) Timer {
|
||||
return &variableTimer{fn: fn, new: c.NewTimer}
|
||||
}
|
||||
|
||||
// Until takes an arbitrary delay function and runs until cancelled or the condition indicates exit. This
|
||||
// offers all of the functionality of the methods in this package.
|
||||
func (fn DelayFunc) Until(ctx context.Context, immediate, sliding bool, condition ConditionWithContextFunc) error {
|
||||
return loopConditionUntilContext(ctx, &variableTimer{fn: fn, new: internalClock.NewTimer}, immediate, sliding, condition)
|
||||
}
|
||||
|
||||
// Concurrent returns a version of this DelayFunc that is safe for use by multiple goroutines that
|
||||
// wish to share a single delay timer.
|
||||
func (fn DelayFunc) Concurrent() DelayFunc {
|
||||
var lock sync.Mutex
|
||||
return func() time.Duration {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
return fn()
|
||||
}
|
||||
}
|
96
vendor/k8s.io/apimachinery/pkg/util/wait/error.go
generated
vendored
Normal file
96
vendor/k8s.io/apimachinery/pkg/util/wait/error.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 wait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrWaitTimeout is returned when the condition was not satisfied in time.
|
||||
//
|
||||
// Deprecated: This type will be made private in favor of Interrupted()
|
||||
// for checking errors or ErrorInterrupted(err) for returning a wrapped error.
|
||||
var ErrWaitTimeout = ErrorInterrupted(errors.New("timed out waiting for the condition"))
|
||||
|
||||
// Interrupted returns true if the error indicates a Poll, ExponentialBackoff, or
|
||||
// Until loop exited for any reason besides the condition returning true or an
|
||||
// error. A loop is considered interrupted if the calling context is cancelled,
|
||||
// the context reaches its deadline, or a backoff reaches its maximum allowed
|
||||
// steps.
|
||||
//
|
||||
// Callers should use this method instead of comparing the error value directly to
|
||||
// ErrWaitTimeout, as methods that cancel a context may not return that error.
|
||||
//
|
||||
// Instead of:
|
||||
//
|
||||
// err := wait.Poll(...)
|
||||
// if err == wait.ErrWaitTimeout {
|
||||
// log.Infof("Wait for operation exceeded")
|
||||
// } else ...
|
||||
//
|
||||
// Use:
|
||||
//
|
||||
// err := wait.Poll(...)
|
||||
// if wait.Interrupted(err) {
|
||||
// log.Infof("Wait for operation exceeded")
|
||||
// } else ...
|
||||
func Interrupted(err error) bool {
|
||||
switch {
|
||||
case errors.Is(err, errWaitTimeout),
|
||||
errors.Is(err, context.Canceled),
|
||||
errors.Is(err, context.DeadlineExceeded):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// errInterrupted
|
||||
type errInterrupted struct {
|
||||
cause error
|
||||
}
|
||||
|
||||
// ErrorInterrupted returns an error that indicates the wait was ended
|
||||
// early for a given reason. If no cause is provided a generic error
|
||||
// will be used but callers are encouraged to provide a real cause for
|
||||
// clarity in debugging.
|
||||
func ErrorInterrupted(cause error) error {
|
||||
switch cause.(type) {
|
||||
case errInterrupted:
|
||||
// no need to wrap twice since errInterrupted is only needed
|
||||
// once in a chain
|
||||
return cause
|
||||
default:
|
||||
return errInterrupted{cause}
|
||||
}
|
||||
}
|
||||
|
||||
// errWaitTimeout is the private version of the previous ErrWaitTimeout
|
||||
// and is private to prevent direct comparison. Use ErrorInterrupted(err)
|
||||
// to get an error that will return true for Interrupted(err).
|
||||
var errWaitTimeout = errInterrupted{}
|
||||
|
||||
func (e errInterrupted) Unwrap() error { return e.cause }
|
||||
func (e errInterrupted) Is(target error) bool { return target == errWaitTimeout }
|
||||
func (e errInterrupted) Error() string {
|
||||
if e.cause == nil {
|
||||
// returns the same error message as historical behavior
|
||||
return "timed out waiting for the condition"
|
||||
}
|
||||
return e.cause.Error()
|
||||
}
|
86
vendor/k8s.io/apimachinery/pkg/util/wait/loop.go
generated
vendored
Normal file
86
vendor/k8s.io/apimachinery/pkg/util/wait/loop.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 wait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// loopConditionUntilContext executes the provided condition at intervals defined by
|
||||
// the provided timer until the provided context is cancelled, the condition returns
|
||||
// true, or the condition returns an error. If sliding is true, the period is computed
|
||||
// after condition runs. If it is false then period includes the runtime for condition.
|
||||
// If immediate is false the first delay happens before any call to condition. The
|
||||
// returned error is the error returned by the last condition or the context error if
|
||||
// the context was terminated.
|
||||
//
|
||||
// This is the common loop construct for all polling in the wait package.
|
||||
func loopConditionUntilContext(ctx context.Context, t Timer, immediate, sliding bool, condition ConditionWithContextFunc) error {
|
||||
defer t.Stop()
|
||||
|
||||
var timeCh <-chan time.Time
|
||||
doneCh := ctx.Done()
|
||||
|
||||
// if we haven't requested immediate execution, delay once
|
||||
if !immediate {
|
||||
timeCh = t.C()
|
||||
select {
|
||||
case <-doneCh:
|
||||
return ctx.Err()
|
||||
case <-timeCh:
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
// checking ctx.Err() is slightly faster than checking a select
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !sliding {
|
||||
t.Next()
|
||||
}
|
||||
if ok, err := func() (bool, error) {
|
||||
defer runtime.HandleCrash()
|
||||
return condition(ctx)
|
||||
}(); err != nil || ok {
|
||||
return err
|
||||
}
|
||||
if sliding {
|
||||
t.Next()
|
||||
}
|
||||
|
||||
if timeCh == nil {
|
||||
timeCh = t.C()
|
||||
}
|
||||
|
||||
// NOTE: b/c there is no priority selection in golang
|
||||
// it is possible for this to race, meaning we could
|
||||
// trigger t.C and doneCh, and t.C select falls through.
|
||||
// In order to mitigate we re-check doneCh at the beginning
|
||||
// of every loop to guarantee at-most one extra execution
|
||||
// of condition.
|
||||
select {
|
||||
case <-doneCh:
|
||||
return ctx.Err()
|
||||
case <-timeCh:
|
||||
}
|
||||
}
|
||||
}
|
315
vendor/k8s.io/apimachinery/pkg/util/wait/poll.go
generated
vendored
Normal file
315
vendor/k8s.io/apimachinery/pkg/util/wait/poll.go
generated
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 wait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PollUntilContextCancel tries a condition func until it returns true, an error, or the context
|
||||
// is cancelled or hits a deadline. condition will be invoked after the first interval if the
|
||||
// context is not cancelled first. The returned error will be from ctx.Err(), the condition's
|
||||
// err return value, or nil. If invoking condition takes longer than interval the next condition
|
||||
// will be invoked immediately. When using very short intervals, condition may be invoked multiple
|
||||
// times before a context cancellation is detected. If immediate is true, condition will be
|
||||
// invoked before waiting and guarantees that condition is invoked at least once, regardless of
|
||||
// whether the context has been cancelled.
|
||||
func PollUntilContextCancel(ctx context.Context, interval time.Duration, immediate bool, condition ConditionWithContextFunc) error {
|
||||
return loopConditionUntilContext(ctx, Backoff{Duration: interval}.Timer(), immediate, false, condition)
|
||||
}
|
||||
|
||||
// PollUntilContextTimeout will terminate polling after timeout duration by setting a context
|
||||
// timeout. This is provided as a convenience function for callers not currently executing under
|
||||
// a deadline and is equivalent to:
|
||||
//
|
||||
// deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout)
|
||||
// err := PollUntilContextCancel(ctx, interval, immediate, condition)
|
||||
//
|
||||
// The deadline context will be cancelled if the Poll succeeds before the timeout, simplifying
|
||||
// inline usage. All other behavior is identical to PollWithContextTimeout.
|
||||
func PollUntilContextTimeout(ctx context.Context, interval, timeout time.Duration, immediate bool, condition ConditionWithContextFunc) error {
|
||||
deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout)
|
||||
defer deadlineCancel()
|
||||
return loopConditionUntilContext(deadlineCtx, Backoff{Duration: interval}.Timer(), immediate, false, condition)
|
||||
}
|
||||
|
||||
// Poll tries a condition func until it returns true, an error, or the timeout
|
||||
// is reached.
|
||||
//
|
||||
// Poll always waits the interval before the run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to Poll something forever, see PollInfinite.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextTimeout.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func Poll(interval, timeout time.Duration, condition ConditionFunc) error {
|
||||
return PollWithContext(context.Background(), interval, timeout, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollWithContext tries a condition func until it returns true, an error,
|
||||
// or when the context expires or the timeout is reached, whichever
|
||||
// happens first.
|
||||
//
|
||||
// PollWithContext always waits the interval before the run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to Poll something forever, see PollInfinite.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextTimeout.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, false, poller(interval, timeout), condition)
|
||||
}
|
||||
|
||||
// PollUntil tries a condition func until it returns true, an error or stopCh is
|
||||
// closed.
|
||||
//
|
||||
// PollUntil always waits interval before the first run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
|
||||
return PollUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollUntilWithContext tries a condition func until it returns true,
|
||||
// an error or the specified context is cancelled or expired.
|
||||
//
|
||||
// PollUntilWithContext always waits interval before the first run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, false, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// PollInfinite tries a condition func until it returns true or an error
|
||||
//
|
||||
// PollInfinite always waits the interval before the run of 'condition'.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollInfinite(interval time.Duration, condition ConditionFunc) error {
|
||||
return PollInfiniteWithContext(context.Background(), interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollInfiniteWithContext tries a condition func until it returns true or an error
|
||||
//
|
||||
// PollInfiniteWithContext always waits the interval before the run of 'condition'.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, false, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// PollImmediate tries a condition func until it returns true, an error, or the timeout
|
||||
// is reached.
|
||||
//
|
||||
// PollImmediate always checks 'condition' before waiting for the interval. 'condition'
|
||||
// will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to immediately Poll something forever, see PollImmediateInfinite.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextTimeout.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error {
|
||||
return PollImmediateWithContext(context.Background(), interval, timeout, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollImmediateWithContext tries a condition func until it returns true, an error,
|
||||
// or the timeout is reached or the specified context expires, whichever happens first.
|
||||
//
|
||||
// PollImmediateWithContext always checks 'condition' before waiting for the interval.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to immediately Poll something forever, see PollImmediateInfinite.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextTimeout.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollImmediateWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, true, poller(interval, timeout), condition)
|
||||
}
|
||||
|
||||
// PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed.
|
||||
//
|
||||
// PollImmediateUntil runs the 'condition' before waiting for the interval.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
|
||||
return PollImmediateUntilWithContext(ContextForChannel(stopCh), interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollImmediateUntilWithContext tries a condition func until it returns true,
|
||||
// an error or the specified context is cancelled or expired.
|
||||
//
|
||||
// PollImmediateUntilWithContext runs the 'condition' before waiting for the interval.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollImmediateUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, true, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// PollImmediateInfinite tries a condition func until it returns true or an error
|
||||
//
|
||||
// PollImmediateInfinite runs the 'condition' before waiting for the interval.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error {
|
||||
return PollImmediateInfiniteWithContext(context.Background(), interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollImmediateInfiniteWithContext tries a condition func until it returns true
|
||||
// or an error or the specified context gets cancelled or expired.
|
||||
//
|
||||
// PollImmediateInfiniteWithContext runs the 'condition' before waiting for the interval.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// Deprecated: This method does not return errors from context, use PollWithContextCancel.
|
||||
// Note that the new method will no longer return ErrWaitTimeout and instead return errors
|
||||
// defined by the context package. Will be removed in a future release.
|
||||
func PollImmediateInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, true, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// Internally used, each of the public 'Poll*' function defined in this
|
||||
// package should invoke this internal function with appropriate parameters.
|
||||
// ctx: the context specified by the caller, for infinite polling pass
|
||||
// a context that never gets cancelled or expired.
|
||||
// immediate: if true, the 'condition' will be invoked before waiting for the interval,
|
||||
// in this case 'condition' will always be invoked at least once.
|
||||
// wait: user specified WaitFunc function that controls at what interval the condition
|
||||
// function should be invoked periodically and whether it is bound by a timeout.
|
||||
// condition: user specified ConditionWithContextFunc function.
|
||||
//
|
||||
// Deprecated: will be removed in favor of loopConditionUntilContext.
|
||||
func poll(ctx context.Context, immediate bool, wait waitWithContextFunc, condition ConditionWithContextFunc) error {
|
||||
if immediate {
|
||||
done, err := runConditionWithCrashProtectionWithContext(ctx, condition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if done {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// returning ctx.Err() will break backward compatibility, use new PollUntilContext*
|
||||
// methods instead
|
||||
return ErrWaitTimeout
|
||||
default:
|
||||
return waitForWithContext(ctx, wait, condition)
|
||||
}
|
||||
}
|
||||
|
||||
// poller returns a WaitFunc that will send to the channel every interval until
|
||||
// timeout has elapsed and then closes the channel.
|
||||
//
|
||||
// Over very short intervals you may receive no ticks before the channel is
|
||||
// closed. A timeout of 0 is interpreted as an infinity, and in such a case
|
||||
// it would be the caller's responsibility to close the done channel.
|
||||
// Failure to do so would result in a leaked goroutine.
|
||||
//
|
||||
// Output ticks are not buffered. If the channel is not ready to receive an
|
||||
// item, the tick is skipped.
|
||||
//
|
||||
// Deprecated: Will be removed in a future release.
|
||||
func poller(interval, timeout time.Duration) waitWithContextFunc {
|
||||
return waitWithContextFunc(func(ctx context.Context) <-chan struct{} {
|
||||
ch := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
|
||||
tick := time.NewTicker(interval)
|
||||
defer tick.Stop()
|
||||
|
||||
var after <-chan time.Time
|
||||
if timeout != 0 {
|
||||
// time.After is more convenient, but it
|
||||
// potentially leaves timers around much longer
|
||||
// than necessary if we exit early.
|
||||
timer := time.NewTimer(timeout)
|
||||
after = timer.C
|
||||
defer timer.Stop()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
// If the consumer isn't ready for this signal drop it and
|
||||
// check the other channels.
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case <-after:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
})
|
||||
}
|
121
vendor/k8s.io/apimachinery/pkg/util/wait/timer.go
generated
vendored
Normal file
121
vendor/k8s.io/apimachinery/pkg/util/wait/timer.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
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 wait
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// Timer abstracts how wait functions interact with time runtime efficiently. Test
|
||||
// code may implement this interface directly but package consumers are encouraged
|
||||
// to use the Backoff type as the primary mechanism for acquiring a Timer. The
|
||||
// interface is a simplification of clock.Timer to prevent misuse. Timers are not
|
||||
// expected to be safe for calls from multiple goroutines.
|
||||
type Timer interface {
|
||||
// C returns a channel that will receive a struct{} each time the timer fires.
|
||||
// The channel should not be waited on after Stop() is invoked. It is allowed
|
||||
// to cache the returned value of C() for the lifetime of the Timer.
|
||||
C() <-chan time.Time
|
||||
// Next is invoked by wait functions to signal timers that the next interval
|
||||
// should begin. You may only use Next() if you have drained the channel C().
|
||||
// You should not call Next() after Stop() is invoked.
|
||||
Next()
|
||||
// Stop releases the timer. It is safe to invoke if no other methods have been
|
||||
// called.
|
||||
Stop()
|
||||
}
|
||||
|
||||
type noopTimer struct {
|
||||
closedCh <-chan time.Time
|
||||
}
|
||||
|
||||
// newNoopTimer creates a timer with a unique channel to avoid contention
|
||||
// for the channel's lock across multiple unrelated timers.
|
||||
func newNoopTimer() noopTimer {
|
||||
ch := make(chan time.Time)
|
||||
close(ch)
|
||||
return noopTimer{closedCh: ch}
|
||||
}
|
||||
|
||||
func (t noopTimer) C() <-chan time.Time {
|
||||
return t.closedCh
|
||||
}
|
||||
func (noopTimer) Next() {}
|
||||
func (noopTimer) Stop() {}
|
||||
|
||||
type variableTimer struct {
|
||||
fn DelayFunc
|
||||
t clock.Timer
|
||||
new func(time.Duration) clock.Timer
|
||||
}
|
||||
|
||||
func (t *variableTimer) C() <-chan time.Time {
|
||||
if t.t == nil {
|
||||
d := t.fn()
|
||||
t.t = t.new(d)
|
||||
}
|
||||
return t.t.C()
|
||||
}
|
||||
func (t *variableTimer) Next() {
|
||||
if t.t == nil {
|
||||
return
|
||||
}
|
||||
d := t.fn()
|
||||
t.t.Reset(d)
|
||||
}
|
||||
func (t *variableTimer) Stop() {
|
||||
if t.t == nil {
|
||||
return
|
||||
}
|
||||
t.t.Stop()
|
||||
t.t = nil
|
||||
}
|
||||
|
||||
type fixedTimer struct {
|
||||
interval time.Duration
|
||||
t clock.Ticker
|
||||
new func(time.Duration) clock.Ticker
|
||||
}
|
||||
|
||||
func (t *fixedTimer) C() <-chan time.Time {
|
||||
if t.t == nil {
|
||||
t.t = t.new(t.interval)
|
||||
}
|
||||
return t.t.C()
|
||||
}
|
||||
func (t *fixedTimer) Next() {
|
||||
// no-op for fixed timers
|
||||
}
|
||||
func (t *fixedTimer) Stop() {
|
||||
if t.t == nil {
|
||||
return
|
||||
}
|
||||
t.t.Stop()
|
||||
t.t = nil
|
||||
}
|
||||
|
||||
var (
|
||||
// RealTimer can be passed to methods that need a clock.Timer.
|
||||
RealTimer = clock.RealClock{}.NewTimer
|
||||
)
|
||||
|
||||
var (
|
||||
// internalClock is used for test injection of clocks
|
||||
internalClock = clock.RealClock{}
|
||||
)
|
650
vendor/k8s.io/apimachinery/pkg/util/wait/wait.go
generated
vendored
650
vendor/k8s.io/apimachinery/pkg/util/wait/wait.go
generated
vendored
@ -18,14 +18,11 @@ package wait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// For any test of the style:
|
||||
@ -83,113 +80,6 @@ func Forever(f func(), period time.Duration) {
|
||||
Until(f, period, NeverStop)
|
||||
}
|
||||
|
||||
// Until loops until stop channel is closed, running f every period.
|
||||
//
|
||||
// Until is syntactic sugar on top of JitterUntil with zero jitter factor and
|
||||
// with sliding = true (which means the timer for period starts after the f
|
||||
// completes).
|
||||
func Until(f func(), period time.Duration, stopCh <-chan struct{}) {
|
||||
JitterUntil(f, period, 0.0, true, stopCh)
|
||||
}
|
||||
|
||||
// UntilWithContext loops until context is done, running f every period.
|
||||
//
|
||||
// UntilWithContext is syntactic sugar on top of JitterUntilWithContext
|
||||
// with zero jitter factor and with sliding = true (which means the timer
|
||||
// for period starts after the f completes).
|
||||
func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) {
|
||||
JitterUntilWithContext(ctx, f, period, 0.0, true)
|
||||
}
|
||||
|
||||
// NonSlidingUntil loops until stop channel is closed, running f every
|
||||
// period.
|
||||
//
|
||||
// NonSlidingUntil is syntactic sugar on top of JitterUntil with zero jitter
|
||||
// factor, with sliding = false (meaning the timer for period starts at the same
|
||||
// time as the function starts).
|
||||
func NonSlidingUntil(f func(), period time.Duration, stopCh <-chan struct{}) {
|
||||
JitterUntil(f, period, 0.0, false, stopCh)
|
||||
}
|
||||
|
||||
// NonSlidingUntilWithContext loops until context is done, running f every
|
||||
// period.
|
||||
//
|
||||
// NonSlidingUntilWithContext is syntactic sugar on top of JitterUntilWithContext
|
||||
// with zero jitter factor, with sliding = false (meaning the timer for period
|
||||
// starts at the same time as the function starts).
|
||||
func NonSlidingUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) {
|
||||
JitterUntilWithContext(ctx, f, period, 0.0, false)
|
||||
}
|
||||
|
||||
// JitterUntil loops until stop channel is closed, running f every period.
|
||||
//
|
||||
// If jitterFactor is positive, the period is jittered before every run of f.
|
||||
// If jitterFactor is not positive, the period is unchanged and not jittered.
|
||||
//
|
||||
// If sliding is true, the period is computed after f runs. If it is false then
|
||||
// period includes the runtime for f.
|
||||
//
|
||||
// Close stopCh to stop. f may not be invoked if stop channel is already
|
||||
// closed. Pass NeverStop to if you don't want it stop.
|
||||
func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) {
|
||||
BackoffUntil(f, NewJitteredBackoffManager(period, jitterFactor, &clock.RealClock{}), sliding, stopCh)
|
||||
}
|
||||
|
||||
// BackoffUntil loops until stop channel is closed, run f every duration given by BackoffManager.
|
||||
//
|
||||
// If sliding is true, the period is computed after f runs. If it is false then
|
||||
// period includes the runtime for f.
|
||||
func BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan struct{}) {
|
||||
var t clock.Timer
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if !sliding {
|
||||
t = backoff.Backoff()
|
||||
}
|
||||
|
||||
func() {
|
||||
defer runtime.HandleCrash()
|
||||
f()
|
||||
}()
|
||||
|
||||
if sliding {
|
||||
t = backoff.Backoff()
|
||||
}
|
||||
|
||||
// NOTE: b/c there is no priority selection in golang
|
||||
// it is possible for this to race, meaning we could
|
||||
// trigger t.C and stopCh, and t.C select falls through.
|
||||
// In order to mitigate we re-check stopCh at the beginning
|
||||
// of every loop to prevent extra executions of f().
|
||||
select {
|
||||
case <-stopCh:
|
||||
if !t.Stop() {
|
||||
<-t.C()
|
||||
}
|
||||
return
|
||||
case <-t.C():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JitterUntilWithContext loops until context is done, running f every period.
|
||||
//
|
||||
// If jitterFactor is positive, the period is jittered before every run of f.
|
||||
// If jitterFactor is not positive, the period is unchanged and not jittered.
|
||||
//
|
||||
// If sliding is true, the period is computed after f runs. If it is false then
|
||||
// period includes the runtime for f.
|
||||
//
|
||||
// Cancel context to stop. f may not be invoked if context is already expired.
|
||||
func JitterUntilWithContext(ctx context.Context, f func(context.Context), period time.Duration, jitterFactor float64, sliding bool) {
|
||||
JitterUntil(func() { f(ctx) }, period, jitterFactor, sliding, ctx.Done())
|
||||
}
|
||||
|
||||
// Jitter returns a time.Duration between duration and duration + maxFactor *
|
||||
// duration.
|
||||
//
|
||||
@ -203,9 +93,6 @@ func Jitter(duration time.Duration, maxFactor float64) time.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)
|
||||
@ -223,425 +110,80 @@ func (cf ConditionFunc) WithContext() ConditionWithContextFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// runConditionWithCrashProtection runs a ConditionFunc with crash protection
|
||||
func runConditionWithCrashProtection(condition ConditionFunc) (bool, error) {
|
||||
return runConditionWithCrashProtectionWithContext(context.TODO(), condition.WithContext())
|
||||
// ContextForChannel provides a context that will be treated as cancelled
|
||||
// when the provided parentCh is closed. The implementation returns
|
||||
// context.Canceled for Err() if and only if the parentCh is closed.
|
||||
func ContextForChannel(parentCh <-chan struct{}) context.Context {
|
||||
return channelContext{stopCh: parentCh}
|
||||
}
|
||||
|
||||
// runConditionWithCrashProtectionWithContext runs a
|
||||
// ConditionWithContextFunc with crash protection.
|
||||
var _ context.Context = channelContext{}
|
||||
|
||||
// channelContext will behave as if the context were cancelled when stopCh is
|
||||
// closed.
|
||||
type channelContext struct {
|
||||
stopCh <-chan struct{}
|
||||
}
|
||||
|
||||
func (c channelContext) Done() <-chan struct{} { return c.stopCh }
|
||||
func (c channelContext) Err() error {
|
||||
select {
|
||||
case <-c.stopCh:
|
||||
return context.Canceled
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func (c channelContext) Deadline() (time.Time, bool) { return time.Time{}, false }
|
||||
func (c channelContext) Value(key any) any { return nil }
|
||||
|
||||
// runConditionWithCrashProtection runs a ConditionFunc with crash protection.
|
||||
//
|
||||
// Deprecated: Will be removed when the legacy polling methods are removed.
|
||||
func runConditionWithCrashProtection(condition ConditionFunc) (bool, error) {
|
||||
defer runtime.HandleCrash()
|
||||
return condition()
|
||||
}
|
||||
|
||||
// runConditionWithCrashProtectionWithContext runs a ConditionWithContextFunc
|
||||
// with crash protection.
|
||||
//
|
||||
// Deprecated: Will be removed when the legacy polling methods are removed.
|
||||
func runConditionWithCrashProtectionWithContext(ctx context.Context, condition ConditionWithContextFunc) (bool, error) {
|
||||
defer runtime.HandleCrash()
|
||||
return condition(ctx)
|
||||
}
|
||||
|
||||
// Backoff holds parameters applied to a Backoff function.
|
||||
type Backoff struct {
|
||||
// The initial duration.
|
||||
Duration time.Duration
|
||||
// Duration is multiplied by factor each iteration, if factor is not zero
|
||||
// and the limits imposed by Steps and Cap have not been reached.
|
||||
// Should not be negative.
|
||||
// The jitter does not contribute to the updates to the duration parameter.
|
||||
Factor float64
|
||||
// The sleep at each iteration is the duration plus an additional
|
||||
// amount chosen uniformly at random from the interval between
|
||||
// zero and `jitter*duration`.
|
||||
Jitter float64
|
||||
// The remaining number of iterations in which the duration
|
||||
// parameter may change (but progress can be stopped earlier by
|
||||
// hitting the cap). If not positive, the duration is not
|
||||
// changed. Used for exponential backoff in combination with
|
||||
// Factor and Cap.
|
||||
Steps int
|
||||
// A limit on revised values of the duration parameter. If a
|
||||
// multiplication by the factor parameter would make the duration
|
||||
// exceed the cap then the duration is set to the cap and the
|
||||
// steps parameter is set to zero.
|
||||
Cap time.Duration
|
||||
}
|
||||
|
||||
// Step (1) returns an amount of time to sleep determined by the
|
||||
// original Duration and Jitter and (2) mutates the provided Backoff
|
||||
// to update its Steps and Duration.
|
||||
func (b *Backoff) Step() time.Duration {
|
||||
if b.Steps < 1 {
|
||||
if b.Jitter > 0 {
|
||||
return Jitter(b.Duration, b.Jitter)
|
||||
}
|
||||
return b.Duration
|
||||
}
|
||||
b.Steps--
|
||||
|
||||
duration := b.Duration
|
||||
|
||||
// calculate the next step
|
||||
if b.Factor != 0 {
|
||||
b.Duration = time.Duration(float64(b.Duration) * b.Factor)
|
||||
if b.Cap > 0 && b.Duration > b.Cap {
|
||||
b.Duration = b.Cap
|
||||
b.Steps = 0
|
||||
}
|
||||
}
|
||||
|
||||
if b.Jitter > 0 {
|
||||
duration = Jitter(duration, b.Jitter)
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
// ContextForChannel derives a child context from a parent channel.
|
||||
//
|
||||
// The derived context's Done channel is closed when the returned cancel function
|
||||
// is called or when the parent channel is closed, whichever happens first.
|
||||
//
|
||||
// Note the caller must *always* call the CancelFunc, otherwise resources may be leaked.
|
||||
func ContextForChannel(parentCh <-chan struct{}) (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-parentCh:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
// BackoffManager manages backoff with a particular scheme based on its underlying implementation. It provides
|
||||
// an interface to return a timer for backoff, and caller shall backoff until Timer.C() drains. If the second Backoff()
|
||||
// is called before the timer from the first Backoff() call finishes, the first timer will NOT be drained and result in
|
||||
// undetermined behavior.
|
||||
// The BackoffManager is supposed to be called in a single-threaded environment.
|
||||
type BackoffManager interface {
|
||||
Backoff() clock.Timer
|
||||
}
|
||||
|
||||
type exponentialBackoffManagerImpl struct {
|
||||
backoff *Backoff
|
||||
backoffTimer clock.Timer
|
||||
lastBackoffStart time.Time
|
||||
initialBackoff time.Duration
|
||||
backoffResetDuration time.Duration
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// NewExponentialBackoffManager returns a manager for managing exponential backoff. Each backoff is jittered and
|
||||
// backoff will not exceed the given max. If the backoff is not called within resetDuration, the backoff is reset.
|
||||
// This backoff manager is used to reduce load during upstream unhealthiness.
|
||||
func NewExponentialBackoffManager(initBackoff, maxBackoff, resetDuration time.Duration, backoffFactor, jitter float64, c clock.Clock) BackoffManager {
|
||||
return &exponentialBackoffManagerImpl{
|
||||
backoff: &Backoff{
|
||||
Duration: initBackoff,
|
||||
Factor: backoffFactor,
|
||||
Jitter: jitter,
|
||||
|
||||
// the current impl of wait.Backoff returns Backoff.Duration once steps are used up, which is not
|
||||
// what we ideally need here, we set it to max int and assume we will never use up the steps
|
||||
Steps: math.MaxInt32,
|
||||
Cap: maxBackoff,
|
||||
},
|
||||
backoffTimer: nil,
|
||||
initialBackoff: initBackoff,
|
||||
lastBackoffStart: c.Now(),
|
||||
backoffResetDuration: resetDuration,
|
||||
clock: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *exponentialBackoffManagerImpl) getNextBackoff() time.Duration {
|
||||
if b.clock.Now().Sub(b.lastBackoffStart) > b.backoffResetDuration {
|
||||
b.backoff.Steps = math.MaxInt32
|
||||
b.backoff.Duration = b.initialBackoff
|
||||
}
|
||||
b.lastBackoffStart = b.clock.Now()
|
||||
return b.backoff.Step()
|
||||
}
|
||||
|
||||
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for exponential backoff.
|
||||
// The returned timer must be drained before calling Backoff() the second time
|
||||
func (b *exponentialBackoffManagerImpl) Backoff() clock.Timer {
|
||||
if b.backoffTimer == nil {
|
||||
b.backoffTimer = b.clock.NewTimer(b.getNextBackoff())
|
||||
} else {
|
||||
b.backoffTimer.Reset(b.getNextBackoff())
|
||||
}
|
||||
return b.backoffTimer
|
||||
}
|
||||
|
||||
type jitteredBackoffManagerImpl struct {
|
||||
clock clock.Clock
|
||||
duration time.Duration
|
||||
jitter float64
|
||||
backoffTimer clock.Timer
|
||||
}
|
||||
|
||||
// NewJitteredBackoffManager returns a BackoffManager that backoffs with given duration plus given jitter. If the jitter
|
||||
// is negative, backoff will not be jittered.
|
||||
func NewJitteredBackoffManager(duration time.Duration, jitter float64, c clock.Clock) BackoffManager {
|
||||
return &jitteredBackoffManagerImpl{
|
||||
clock: c,
|
||||
duration: duration,
|
||||
jitter: jitter,
|
||||
backoffTimer: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jitteredBackoffManagerImpl) getNextBackoff() time.Duration {
|
||||
jitteredPeriod := j.duration
|
||||
if j.jitter > 0.0 {
|
||||
jitteredPeriod = Jitter(j.duration, j.jitter)
|
||||
}
|
||||
return jitteredPeriod
|
||||
}
|
||||
|
||||
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for jittered backoff.
|
||||
// The returned timer must be drained before calling Backoff() the second time
|
||||
func (j *jitteredBackoffManagerImpl) Backoff() clock.Timer {
|
||||
backoff := j.getNextBackoff()
|
||||
if j.backoffTimer == nil {
|
||||
j.backoffTimer = j.clock.NewTimer(backoff)
|
||||
} else {
|
||||
j.backoffTimer.Reset(backoff)
|
||||
}
|
||||
return j.backoffTimer
|
||||
}
|
||||
|
||||
// ExponentialBackoff repeats a condition check with exponential backoff.
|
||||
//
|
||||
// It repeatedly checks the condition and then sleeps, using `backoff.Step()`
|
||||
// to determine the length of the sleep and adjust Duration and Steps.
|
||||
// Stops and returns as soon as:
|
||||
// 1. the condition check returns true or an error,
|
||||
// 2. `backoff.Steps` checks of the condition have been done, or
|
||||
// 3. a sleep truncated by the cap on duration has been completed.
|
||||
// In case (1) the returned error is what the condition function returned.
|
||||
// In all other cases, ErrWaitTimeout is returned.
|
||||
func ExponentialBackoff(backoff Backoff, condition ConditionFunc) error {
|
||||
for backoff.Steps > 0 {
|
||||
if ok, err := runConditionWithCrashProtection(condition); err != nil || ok {
|
||||
return err
|
||||
}
|
||||
if backoff.Steps == 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(backoff.Step())
|
||||
}
|
||||
return ErrWaitTimeout
|
||||
}
|
||||
|
||||
// Poll tries a condition func until it returns true, an error, or the timeout
|
||||
// is reached.
|
||||
//
|
||||
// Poll always waits the interval before the run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to Poll something forever, see PollInfinite.
|
||||
func Poll(interval, timeout time.Duration, condition ConditionFunc) error {
|
||||
return PollWithContext(context.Background(), interval, timeout, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollWithContext tries a condition func until it returns true, an error,
|
||||
// or when the context expires or the timeout is reached, whichever
|
||||
// happens first.
|
||||
//
|
||||
// PollWithContext always waits the interval before the run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to Poll something forever, see PollInfinite.
|
||||
func PollWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, false, poller(interval, timeout), condition)
|
||||
}
|
||||
|
||||
// PollUntil tries a condition func until it returns true, an error or stopCh is
|
||||
// closed.
|
||||
//
|
||||
// PollUntil always waits interval before the first run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
func PollUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
|
||||
ctx, cancel := ContextForChannel(stopCh)
|
||||
defer cancel()
|
||||
return PollUntilWithContext(ctx, interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollUntilWithContext tries a condition func until it returns true,
|
||||
// an error or the specified context is cancelled or expired.
|
||||
//
|
||||
// PollUntilWithContext always waits interval before the first run of 'condition'.
|
||||
// 'condition' will always be invoked at least once.
|
||||
func PollUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, false, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// PollInfinite tries a condition func until it returns true or an error
|
||||
//
|
||||
// PollInfinite always waits the interval before the run of 'condition'.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
func PollInfinite(interval time.Duration, condition ConditionFunc) error {
|
||||
return PollInfiniteWithContext(context.Background(), interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollInfiniteWithContext tries a condition func until it returns true or an error
|
||||
//
|
||||
// PollInfiniteWithContext always waits the interval before the run of 'condition'.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
func PollInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, false, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// PollImmediate tries a condition func until it returns true, an error, or the timeout
|
||||
// is reached.
|
||||
//
|
||||
// PollImmediate always checks 'condition' before waiting for the interval. 'condition'
|
||||
// will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to immediately Poll something forever, see PollImmediateInfinite.
|
||||
func PollImmediate(interval, timeout time.Duration, condition ConditionFunc) error {
|
||||
return PollImmediateWithContext(context.Background(), interval, timeout, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollImmediateWithContext tries a condition func until it returns true, an error,
|
||||
// or the timeout is reached or the specified context expires, whichever happens first.
|
||||
//
|
||||
// PollImmediateWithContext always checks 'condition' before waiting for the interval.
|
||||
// 'condition' will always be invoked at least once.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
//
|
||||
// If you want to immediately Poll something forever, see PollImmediateInfinite.
|
||||
func PollImmediateWithContext(ctx context.Context, interval, timeout time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, true, poller(interval, timeout), condition)
|
||||
}
|
||||
|
||||
// PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed.
|
||||
//
|
||||
// PollImmediateUntil runs the 'condition' before waiting for the interval.
|
||||
// 'condition' will always be invoked at least once.
|
||||
func PollImmediateUntil(interval time.Duration, condition ConditionFunc, stopCh <-chan struct{}) error {
|
||||
ctx, cancel := ContextForChannel(stopCh)
|
||||
defer cancel()
|
||||
return PollImmediateUntilWithContext(ctx, interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollImmediateUntilWithContext tries a condition func until it returns true,
|
||||
// an error or the specified context is cancelled or expired.
|
||||
//
|
||||
// PollImmediateUntilWithContext runs the 'condition' before waiting for the interval.
|
||||
// 'condition' will always be invoked at least once.
|
||||
func PollImmediateUntilWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, true, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// PollImmediateInfinite tries a condition func until it returns true or an error
|
||||
//
|
||||
// PollImmediateInfinite runs the 'condition' before waiting for the interval.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
func PollImmediateInfinite(interval time.Duration, condition ConditionFunc) error {
|
||||
return PollImmediateInfiniteWithContext(context.Background(), interval, condition.WithContext())
|
||||
}
|
||||
|
||||
// PollImmediateInfiniteWithContext tries a condition func until it returns true
|
||||
// or an error or the specified context gets cancelled or expired.
|
||||
//
|
||||
// PollImmediateInfiniteWithContext runs the 'condition' before waiting for the interval.
|
||||
//
|
||||
// Some intervals may be missed if the condition takes too long or the time
|
||||
// window is too short.
|
||||
func PollImmediateInfiniteWithContext(ctx context.Context, interval time.Duration, condition ConditionWithContextFunc) error {
|
||||
return poll(ctx, true, poller(interval, 0), condition)
|
||||
}
|
||||
|
||||
// Internally used, each of the public 'Poll*' function defined in this
|
||||
// package should invoke this internal function with appropriate parameters.
|
||||
// ctx: the context specified by the caller, for infinite polling pass
|
||||
// a context that never gets cancelled or expired.
|
||||
// immediate: if true, the 'condition' will be invoked before waiting for the interval,
|
||||
// in this case 'condition' will always be invoked at least once.
|
||||
// wait: user specified WaitFunc function that controls at what interval the condition
|
||||
// function should be invoked periodically and whether it is bound by a timeout.
|
||||
// condition: user specified ConditionWithContextFunc function.
|
||||
func poll(ctx context.Context, immediate bool, wait WaitWithContextFunc, condition ConditionWithContextFunc) error {
|
||||
if immediate {
|
||||
done, err := runConditionWithCrashProtectionWithContext(ctx, condition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if done {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// returning ctx.Err() will break backward compatibility
|
||||
return ErrWaitTimeout
|
||||
default:
|
||||
return WaitForWithContext(ctx, wait, condition)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitFunc creates a channel that receives an item every time a test
|
||||
// waitFunc creates a channel that receives an item every time a test
|
||||
// should be executed and is closed when the last test should be invoked.
|
||||
type WaitFunc func(done <-chan struct{}) <-chan struct{}
|
||||
//
|
||||
// Deprecated: Will be removed in a future release in favor of
|
||||
// loopConditionUntilContext.
|
||||
type waitFunc func(done <-chan struct{}) <-chan struct{}
|
||||
|
||||
// WithContext converts the WaitFunc to an equivalent WaitWithContextFunc
|
||||
func (w WaitFunc) WithContext() WaitWithContextFunc {
|
||||
func (w waitFunc) WithContext() waitWithContextFunc {
|
||||
return func(ctx context.Context) <-chan struct{} {
|
||||
return w(ctx.Done())
|
||||
}
|
||||
}
|
||||
|
||||
// WaitWithContextFunc creates a channel that receives an item every time a test
|
||||
// waitWithContextFunc creates a channel that receives an item every time a test
|
||||
// should be executed and is closed when the last test should be invoked.
|
||||
//
|
||||
// When the specified context gets cancelled or expires the function
|
||||
// stops sending item and returns immediately.
|
||||
type WaitWithContextFunc func(ctx context.Context) <-chan struct{}
|
||||
//
|
||||
// Deprecated: Will be removed in a future release in favor of
|
||||
// loopConditionUntilContext.
|
||||
type waitWithContextFunc func(ctx context.Context) <-chan struct{}
|
||||
|
||||
// WaitFor continually checks 'fn' as driven by 'wait'.
|
||||
// waitForWithContext continually checks 'fn' as driven by 'wait'.
|
||||
//
|
||||
// WaitFor gets a channel from 'wait()”, and then invokes 'fn' once for every value
|
||||
// placed on the channel and once more when the channel is closed. If the channel is closed
|
||||
// and 'fn' returns false without error, WaitFor returns ErrWaitTimeout.
|
||||
//
|
||||
// If 'fn' returns an error the loop ends and that error is returned. If
|
||||
// 'fn' returns true the loop ends and nil is returned.
|
||||
//
|
||||
// ErrWaitTimeout will be returned if the 'done' channel is closed without fn ever
|
||||
// returning true.
|
||||
//
|
||||
// When the done channel is closed, because the golang `select` statement is
|
||||
// "uniform pseudo-random", the `fn` might still run one or multiple time,
|
||||
// though eventually `WaitFor` will return.
|
||||
func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error {
|
||||
ctx, cancel := ContextForChannel(done)
|
||||
defer cancel()
|
||||
return WaitForWithContext(ctx, wait.WithContext(), fn.WithContext())
|
||||
}
|
||||
|
||||
// WaitForWithContext continually checks 'fn' as driven by 'wait'.
|
||||
//
|
||||
// WaitForWithContext gets a channel from 'wait()”, and then invokes 'fn'
|
||||
// waitForWithContext gets a channel from 'wait()”, and then invokes 'fn'
|
||||
// once for every value placed on the channel and once more when the
|
||||
// channel is closed. If the channel is closed and 'fn'
|
||||
// returns false without error, WaitForWithContext returns ErrWaitTimeout.
|
||||
// returns false without error, waitForWithContext returns ErrWaitTimeout.
|
||||
//
|
||||
// If 'fn' returns an error the loop ends and that error is returned. If
|
||||
// 'fn' returns true the loop ends and nil is returned.
|
||||
@ -651,8 +193,11 @@ func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error {
|
||||
//
|
||||
// When the ctx.Done() channel is closed, because the golang `select` statement is
|
||||
// "uniform pseudo-random", the `fn` might still run one or multiple times,
|
||||
// though eventually `WaitForWithContext` will return.
|
||||
func WaitForWithContext(ctx context.Context, wait WaitWithContextFunc, fn ConditionWithContextFunc) error {
|
||||
// though eventually `waitForWithContext` will return.
|
||||
//
|
||||
// Deprecated: Will be removed in a future release in favor of
|
||||
// loopConditionUntilContext.
|
||||
func waitForWithContext(ctx context.Context, wait waitWithContextFunc, fn ConditionWithContextFunc) error {
|
||||
waitCtx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
c := wait(waitCtx)
|
||||
@ -670,88 +215,9 @@ func WaitForWithContext(ctx context.Context, wait WaitWithContextFunc, fn Condit
|
||||
return ErrWaitTimeout
|
||||
}
|
||||
case <-ctx.Done():
|
||||
// returning ctx.Err() will break backward compatibility
|
||||
// returning ctx.Err() will break backward compatibility, use new PollUntilContext*
|
||||
// methods instead
|
||||
return ErrWaitTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// poller returns a WaitFunc that will send to the channel every interval until
|
||||
// timeout has elapsed and then closes the channel.
|
||||
//
|
||||
// Over very short intervals you may receive no ticks before the channel is
|
||||
// closed. A timeout of 0 is interpreted as an infinity, and in such a case
|
||||
// it would be the caller's responsibility to close the done channel.
|
||||
// Failure to do so would result in a leaked goroutine.
|
||||
//
|
||||
// Output ticks are not buffered. If the channel is not ready to receive an
|
||||
// item, the tick is skipped.
|
||||
func poller(interval, timeout time.Duration) WaitWithContextFunc {
|
||||
return WaitWithContextFunc(func(ctx context.Context) <-chan struct{} {
|
||||
ch := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
|
||||
tick := time.NewTicker(interval)
|
||||
defer tick.Stop()
|
||||
|
||||
var after <-chan time.Time
|
||||
if timeout != 0 {
|
||||
// time.After is more convenient, but it
|
||||
// potentially leaves timers around much longer
|
||||
// than necessary if we exit early.
|
||||
timer := time.NewTimer(timeout)
|
||||
after = timer.C
|
||||
defer timer.Stop()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
// If the consumer isn't ready for this signal drop it and
|
||||
// check the other channels.
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case <-after:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
})
|
||||
}
|
||||
|
||||
// ExponentialBackoffWithContext works with a request context and a Backoff. It ensures that the retry wait never
|
||||
// exceeds the deadline specified by the request context.
|
||||
func ExponentialBackoffWithContext(ctx context.Context, backoff Backoff, condition ConditionFunc) error {
|
||||
for backoff.Steps > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if ok, err := runConditionWithCrashProtection(condition); err != nil || ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if backoff.Steps == 1 {
|
||||
break
|
||||
}
|
||||
|
||||
waitBeforeRetry := backoff.Step()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(waitBeforeRetry):
|
||||
}
|
||||
}
|
||||
|
||||
return ErrWaitTimeout
|
||||
}
|
||||
|
Reference in New Issue
Block a user