55 "errors"
66 "sync"
77
8+ "github.com/go-logr/logr"
89 "sigs.k8s.io/controller-runtime/pkg/webhook"
910)
1011
@@ -48,6 +49,16 @@ func newRunnables(baseContext BaseContextFunc, errChan chan error) *runnables {
4849 }
4950}
5051
52+ // withLogger returns the runnables with the logger set for all runnable groups.
53+ func (r * runnables ) withLogger (logger logr.Logger ) * runnables {
54+ r .HTTPServers .withLogger (logger )
55+ r .Webhooks .withLogger (logger )
56+ r .Caches .withLogger (logger )
57+ r .LeaderElection .withLogger (logger )
58+ r .Others .withLogger (logger )
59+ return r
60+ }
61+
5162// Add adds a runnable to closest group of runnable that they belong to.
5263//
5364// Add should be able to be called before and after Start, but not after StopAndWait.
@@ -119,6 +130,9 @@ type runnableGroup struct {
119130 // wg is an internal sync.WaitGroup that allows us to properly stop
120131 // and wait for all the runnables to finish before returning.
121132 wg * sync.WaitGroup
133+
134+ // logger is used for logging when errors are dropped during shutdown
135+ logger logr.Logger
122136}
123137
124138func newRunnableGroup (baseContext BaseContextFunc , errChan chan error ) * runnableGroup {
@@ -127,12 +141,18 @@ func newRunnableGroup(baseContext BaseContextFunc, errChan chan error) *runnable
127141 errChan : errChan ,
128142 ch : make (chan * readyRunnable ),
129143 wg : new (sync.WaitGroup ),
144+ logger : logr .Discard (), // Default to no-op logger
130145 }
131146
132147 r .ctx , r .cancel = context .WithCancel (baseContext ())
133148 return r
134149}
135150
151+ // withLogger sets the logger for this runnable group.
152+ func (r * runnableGroup ) withLogger (logger logr.Logger ) {
153+ r .logger = logger
154+ }
155+
136156// Started returns true if the group has started.
137157func (r * runnableGroup ) Started () bool {
138158 r .start .Lock ()
@@ -238,7 +258,27 @@ func (r *runnableGroup) reconcile() {
238258
239259 // Start the runnable.
240260 if err := rn .Start (r .ctx ); err != nil {
241- r .errChan <- err
261+ // Check if we're during the shutdown process.
262+ r .stop .RLock ()
263+ isStopped := r .stopped
264+ r .stop .RUnlock ()
265+
266+ if isStopped {
267+ // During shutdown, try to send error first (error drain goroutine might still be running)
268+ // but drop if it would block to prevent goroutine leaks
269+ select {
270+ case r .errChan <- err :
271+ // Error sent successfully (error drain goroutine is still running)
272+ default :
273+ // Error drain goroutine has exited, drop error to prevent goroutine leak
274+ if ! errors .Is (err , context .Canceled ) { // don't log context.Canceled errors as they are expected during shutdown
275+ r .logger .Info ("error dropped during shutdown to prevent goroutine leak" , "error" , err )
276+ }
277+ }
278+ } else {
279+ // During normal operation, always try to send errors (may block briefly)
280+ r .errChan <- err
281+ }
242282 }
243283 }(runnable )
244284 }
0 commit comments