Skip to content

Commit 7f744be

Browse files
authored
Merge pull request kubernetes#90476 from zhan849/harry/fix-backoff-manager-first-run
fix backoff manager timer initialization race
2 parents 36eee3c + 397319e commit 7f744be

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed

staging/src/k8s.io/apimachinery/pkg/util/wait/wait.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,9 @@ func contextForChannel(parentCh <-chan struct{}) (context.Context, context.Cance
286286
}
287287

288288
// BackoffManager manages backoff with a particular scheme based on its underlying implementation. It provides
289-
// an interface to return a timer for backoff, and caller shall backoff until Timer.C returns. If the second Backoff()
290-
// is called before the timer from the first Backoff() call finishes, the first timer will NOT be drained.
289+
// an interface to return a timer for backoff, and caller shall backoff until Timer.C() drains. If the second Backoff()
290+
// is called before the timer from the first Backoff() call finishes, the first timer will NOT be drained and result in
291+
// undetermined behavior.
291292
// The BackoffManager is supposed to be called in a single-threaded environment.
292293
type BackoffManager interface {
293294
Backoff() clock.Timer
@@ -317,7 +318,7 @@ func NewExponentialBackoffManager(initBackoff, maxBackoff, resetDuration time.Du
317318
Steps: math.MaxInt32,
318319
Cap: maxBackoff,
319320
},
320-
backoffTimer: c.NewTimer(0),
321+
backoffTimer: nil,
321322
initialBackoff: initBackoff,
322323
lastBackoffStart: c.Now(),
323324
backoffResetDuration: resetDuration,
@@ -334,9 +335,14 @@ func (b *exponentialBackoffManagerImpl) getNextBackoff() time.Duration {
334335
return b.backoff.Step()
335336
}
336337

337-
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for backoff.
338+
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for exponential backoff.
339+
// The returned timer must be drained before calling Backoff() the second time
338340
func (b *exponentialBackoffManagerImpl) Backoff() clock.Timer {
339-
b.backoffTimer.Reset(b.getNextBackoff())
341+
if b.backoffTimer == nil {
342+
b.backoffTimer = b.clock.NewTimer(b.getNextBackoff())
343+
} else {
344+
b.backoffTimer.Reset(b.getNextBackoff())
345+
}
340346
return b.backoffTimer
341347
}
342348

@@ -354,7 +360,7 @@ func NewJitteredBackoffManager(duration time.Duration, jitter float64, c clock.C
354360
clock: c,
355361
duration: duration,
356362
jitter: jitter,
357-
backoffTimer: c.NewTimer(0),
363+
backoffTimer: nil,
358364
}
359365
}
360366

@@ -366,8 +372,15 @@ func (j *jitteredBackoffManagerImpl) getNextBackoff() time.Duration {
366372
return jitteredPeriod
367373
}
368374

375+
// Backoff implements BackoffManager.Backoff, it returns a timer so caller can block on the timer for jittered backoff.
376+
// The returned timer must be drained before calling Backoff() the second time
369377
func (j *jitteredBackoffManagerImpl) Backoff() clock.Timer {
370-
j.backoffTimer.Reset(j.getNextBackoff())
378+
backoff := j.getNextBackoff()
379+
if j.backoffTimer == nil {
380+
j.backoffTimer = j.clock.NewTimer(backoff)
381+
} else {
382+
j.backoffTimer.Reset(backoff)
383+
}
371384
return j.backoffTimer
372385
}
373386

staging/src/k8s.io/apimachinery/pkg/util/wait/wait_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,3 +731,30 @@ func TestJitteredBackoffManagerGetNextBackoff(t *testing.T) {
731731
t.Errorf("backoff should be 1, but got %d", backoff)
732732
}
733733
}
734+
735+
func TestJitterBackoffManagerWithRealClock(t *testing.T) {
736+
backoffMgr := NewJitteredBackoffManager(1*time.Millisecond, 0, &clock.RealClock{})
737+
for i := 0; i < 5; i++ {
738+
start := time.Now()
739+
<-backoffMgr.Backoff().C()
740+
passed := time.Now().Sub(start)
741+
if passed < 1*time.Millisecond {
742+
t.Errorf("backoff should be at least 1ms, but got %s", passed.String())
743+
}
744+
}
745+
}
746+
747+
func TestExponentialBackoffManagerWithRealClock(t *testing.T) {
748+
// backoff at least 1ms, 2ms, 4ms, 8ms, 10ms, 10ms, 10ms
749+
durationFactors := []time.Duration{1, 2, 4, 8, 10, 10, 10}
750+
backoffMgr := NewExponentialBackoffManager(1*time.Millisecond, 10*time.Millisecond, 1*time.Hour, 2.0, 0.0, &clock.RealClock{})
751+
752+
for i := range durationFactors {
753+
start := time.Now()
754+
<-backoffMgr.Backoff().C()
755+
passed := time.Now().Sub(start)
756+
if passed < durationFactors[i]*time.Millisecond {
757+
t.Errorf("backoff should be at least %d ms, but got %s", durationFactors[i], passed.String())
758+
}
759+
}
760+
}

0 commit comments

Comments
 (0)