Change:
return duration } <ins> // 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 } </ins> // ExponentialBackoff repeats a condition check with exponential backoff. // // It checks the condition up to Steps times, increasing the wait by multiplying

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