Change:
Users of the function would expect the close of the "done" channel should terminate the . We should change the function to align to users' expectation. We should document what the channel does. This is a behavior change, to be cautious, we should add a release note claiming breaking change. /kind bug
/sig api-machinery cc Feel free to assign to yourself if you have time. Otherwise I'll attempt a fix tomorrow ;)
I'll fix it. /assign
GitHub didn't allow me to assign the following users: kdada. Note that only and repo collaborators can be assigned. For more information please see <detailsIn response to : I'll fix it. Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/cc

Discussion:
stopCh := make(chan struct{}) defer close(stopCh) go gc.Run(1, stopCh) <del> go gc.Sync(fakeDiscoveryClient, 10*time.Millisecond, stopCh) </del> <ins> // The pseudo-code of GarbageCollector.Sync(): // GarbageCollector.Sync(client, period, stopCh): //    wait.Until() loops with `period` until the `stopCh` is closed : //        wait.PollImmediateUntil() loops with 100ms (hardcode) util the `stopCh` is closed: //            GetDeletableResources() //            gc.resyncMonitors() //            controller.WaitForCacheSync() loops with `syncedPollPeriod` (hardcoded to 100ms), until either its stop channel is closed after `period`, or all caches synced. // // Setting the period to 200ms allows the WaitForCacheSync() to check for cache sync ~2 times in every wait.PollImmediateUntil() loop. go gc.Sync(fakeDiscoveryClient, 200*time.Millisecond, stopCh) </ins> // Wait until the sync discovers the initial resources fmt.Printf("Test output")

// WaitFor continually checks 'fn' as driven by 'wait'. // // WaitFor gets a channel from 'wait()'', and then invokes 'fn' once for every value <del> // placed on the channel and once more when the channel is closed. </del> <ins> // 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. </ins> // <del> // If 'fn' returns an error the loop ends and that error is returned, and if </del> <ins> // If 'fn' returns an error the loop ends and that error is returned. If </ins> // 'fn' returns true the loop ends and nil is returned. // <del> // ErrWaitTimeout will be returned if the channel is closed without fn ever </del> <ins> // ErrWaitTimeout will be returned if the 'done' channel is closed without fn ever </ins> // returning true. <ins> // // 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. </ins> func WaitFor(wait WaitFunc, fn ConditionFunc, done <-chan struct{}) error { stopCh := make(chan struct{}) <del> once := sync.Once{} closeCh := func() { once.Do(func() { close(stopCh) }) } defer closeCh() </del> <ins> defer close(stopCh) </ins> c := wait(stopCh) for { select {

return ErrWaitTimeout } case <-done: <del> closeCh() </del> <ins> return ErrWaitTimeout </ins> } } <del> return ErrWaitTimeout </del> } // poller returns a WaitFunc that will send to the channel every interval until

} } <ins> // TestWaitForWithEarlyClosingWaitFunc tests WaitFor when the WaitFunc closes its channel. The WaitFor should // always return ErrWaitTimeout. func TestWaitForWithEarlyClosingWaitFunc(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) start := time.Now() err := WaitFor(func(done <-chan struct{}) <-chan struct{} { c := make(chan struct{}) close(c) return c }, 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") } if err != ErrWaitTimeout { t.Errorf("expected ErrWaitTimeout from WaitFunc") } } // TestWaitForWithClosedChannel tests WaitFor when it receives a closed channel. The WaitFor should // always return ErrWaitTimeout. </ins> func TestWaitForWithClosedChannel(t *testing.T) { stopCh := make(chan struct{}) close(stopCh) <ins> c := make(chan struct{}) defer close(c) </ins> start := time.Now() <del> err := WaitFor(poller(ForeverTestTimeout, ForeverTestTimeout), func() (bool, error) { </del> <ins> err := WaitFor(func(done <-chan struct{}) <-chan struct{} { return c }, func() (bool, error) { </ins> return false, nil }, stopCh) duration := time.Now().Sub(start)