Change:
// 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 { <del> return WaitFor(poller(interval, 0), condition, stopCh) </del> <ins> ctx, cancel := contextForChannel(stopCh) defer cancel() return WaitFor(poller(interval, 0), condition, ctx.Done()) </ins> } // PollImmediateUntil tries a condition func until it returns true, an error or stopCh is closed.

Discussion:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!--Related to , there remains a leak in which does not ensure the channel it passes to is closed. This causes goroutines to leak under common scenarios, such as when calling . /cc What happened: Each call to leaks an active goroutine (in this case spawned by via . The goroutine will not end until the passed to is closed - however this requirement can and should be handled internally. Often callers will pass a context with a longer lifespan. What you expected to happen: should ensure that the polling goroutine is ended after its condition is met. This can be accomplished by creating a new channel with a child cancellation relationship to the original, and passing that on instead. This child channel can be closed once returns. How to reproduce it (as minimally and precisely as possible): The minimal reproduction would be to call with a true condition, and not close the passed . However, a more complete reproduction involving a can be seen . Anything else we need to know?: This leak was discovered in practice and caused a steady leak of memory and cpu in a project I work on. Environment: Kubernetes version (use ):Cloud provider or hardware configuration: Google GKE OS (e.g. from /etc/os-release): n/a Kernel (e.g. ): n/a Install tools: n/a Others: n/a <!-- DO NOT EDIT BELOW THIS LINE --/kind bug
/sig api-machinery
fyi Oren has 2 options to fix this. He'll be pasting sample code shortly and we can decide which approach we prefer.
Hi everyone! As promised, I've solved this two similar ways, and wanted to get your temperature on them before submitting the PR. I can then create a child context and pass its done channel to : This ensures that poller's goroutine will be cleaned up either when stopCh is closed or when the condition is met, whichever comes first. If there is aversion to using contexts here, we can mimic the subset of functionality from context that we need (safe to cancel from multiple goroutines + and a parent-child cancellation relationship): I lean towards option 1 and contexts - their semantics are familiar and result in less code on our end. Let me know what you think!

I would prefer using contexts - we actually should in the long term have context variants of all waits, so it’s the right direction to start using them more here
+1 to option 1
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
Stale issues rot after 30d of inactivity. Mark the issue as fresh with . Rotten issues close after an additional 30d of inactivity. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle rotten
/remove-lifecycle rotten