Merge pull request #70277 from kdada/master

Fix goroutine leak of wait.poller
This commit is contained in:
Kubernetes Prow Robot 2018-12-26 22:29:47 -08:00 committed by GitHub
commit 81a1f12dab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 20 deletions

View File

@ -359,18 +359,30 @@ type WaitFunc func(done <-chan struct{}) <-chan struct{}
// ErrWaitTimeout will be returned if the channel is closed without fn ever // ErrWaitTimeout will be returned if the channel is closed without fn ever
// returning true. // returning true.
func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error { func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error {
c := wait(done) stopCh := make(chan struct{})
once := sync.Once{}
closeCh := func() {
once.Do(func() {
close(stopCh)
})
}
defer closeCh()
c := wait(stopCh)
for { for {
_, open := <-c select {
ok, err := fn() case _, open := <-c:
if err != nil { ok, err := fn()
return err if err != nil {
} return err
if ok { }
return nil if ok {
} return nil
if !open { }
break if !open {
return ErrWaitTimeout
}
case <-done:
closeCh()
} }
} }
return ErrWaitTimeout return ErrWaitTimeout

View File

@ -456,18 +456,46 @@ func TestWaitFor(t *testing.T) {
} }
} }
func TestWaitForWithDelay(t *testing.T) { func TestWaitForWithClosedChannel(t *testing.T) {
done := make(chan struct{}) stopCh := make(chan struct{})
defer close(done) close(stopCh)
WaitFor(poller(time.Millisecond, ForeverTestTimeout), func() (bool, error) { start := time.Now()
err := WaitFor(poller(ForeverTestTimeout, ForeverTestTimeout), func() (bool, error) {
return false, nil
}, stopCh)
duration := time.Now().Sub(start)
// The WaitFor should return immediately, so the duration is close to 0s.
if duration >= ForeverTestTimeout/2 {
t.Errorf("expected short timeout duration")
}
// The interval of the poller is ForeverTestTimeout, so the WaitFor should always return ErrWaitTimeout.
if err != ErrWaitTimeout {
t.Errorf("expected ErrWaitTimeout from WaitFunc")
}
}
// TestWaitForClosesStopCh verifies that after the condition func returns true, WaitFor() closes the stop channel it supplies to the WaitFunc.
func TestWaitForClosesStopCh(t *testing.T) {
stopCh := make(chan struct{})
defer close(stopCh)
waitFunc := poller(time.Millisecond, ForeverTestTimeout)
var doneCh <-chan struct{}
WaitFor(func(done <-chan struct{}) <-chan struct{} {
doneCh = done
return waitFunc(done)
}, func() (bool, error) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
return true, nil return true, nil
}, done) }, stopCh)
// If polling goroutine doesn't see the done signal it will leak timers. // The polling goroutine should be closed after WaitFor returning.
select { select {
case done <- struct{}{}: case _, ok := <-doneCh:
case <-time.After(ForeverTestTimeout): if ok {
t.Errorf("expected an ack of the done signal.") t.Errorf("expected closed channel after WaitFunc returning")
}
default:
t.Errorf("expected an ack of the done signal")
} }
} }