5
5
"errors"
6
6
"sync"
7
7
8
+ "github.com/go-logr/logr"
8
9
"sigs.k8s.io/controller-runtime/pkg/webhook"
9
10
)
10
11
@@ -48,6 +49,16 @@ func newRunnables(baseContext BaseContextFunc, errChan chan error) *runnables {
48
49
}
49
50
}
50
51
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
+
51
62
// Add adds a runnable to closest group of runnable that they belong to.
52
63
//
53
64
// Add should be able to be called before and after Start, but not after StopAndWait.
@@ -119,6 +130,9 @@ type runnableGroup struct {
119
130
// wg is an internal sync.WaitGroup that allows us to properly stop
120
131
// and wait for all the runnables to finish before returning.
121
132
wg * sync.WaitGroup
133
+
134
+ // logger is used for logging when errors are dropped during shutdown
135
+ logger logr.Logger
122
136
}
123
137
124
138
func newRunnableGroup (baseContext BaseContextFunc , errChan chan error ) * runnableGroup {
@@ -127,12 +141,18 @@ func newRunnableGroup(baseContext BaseContextFunc, errChan chan error) *runnable
127
141
errChan : errChan ,
128
142
ch : make (chan * readyRunnable ),
129
143
wg : new (sync.WaitGroup ),
144
+ logger : logr .Discard (), // Default to no-op logger
130
145
}
131
146
132
147
r .ctx , r .cancel = context .WithCancel (baseContext ())
133
148
return r
134
149
}
135
150
151
+ // withLogger sets the logger for this runnable group.
152
+ func (r * runnableGroup ) withLogger (logger logr.Logger ) {
153
+ r .logger = logger
154
+ }
155
+
136
156
// Started returns true if the group has started.
137
157
func (r * runnableGroup ) Started () bool {
138
158
r .start .Lock ()
@@ -238,7 +258,27 @@ func (r *runnableGroup) reconcile() {
238
258
239
259
// Start the runnable.
240
260
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
+ }
242
282
}
243
283
}(runnable )
244
284
}
0 commit comments