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
@@ -46,6 +47,16 @@ func newRunnables(baseContext BaseContextFunc, errChan chan error) *runnables {
46
47
}
47
48
}
48
49
50
+ // withLogger sets the logger for all runnable groups.
51
+ func (r * runnables ) withLogger (logger logr.Logger ) * runnables {
52
+ r .HTTPServers .withLogger (logger )
53
+ r .Webhooks .withLogger (logger )
54
+ r .Caches .withLogger (logger )
55
+ r .LeaderElection .withLogger (logger )
56
+ r .Others .withLogger (logger )
57
+ return r
58
+ }
59
+
49
60
// Add adds a runnable to closest group of runnable that they belong to.
50
61
//
51
62
// Add should be able to be called before and after Start, but not after StopAndWait.
@@ -105,6 +116,9 @@ type runnableGroup struct {
105
116
// wg is an internal sync.WaitGroup that allows us to properly stop
106
117
// and wait for all the runnables to finish before returning.
107
118
wg * sync.WaitGroup
119
+
120
+ // logger is used for logging when errors are dropped during shutdown
121
+ logger logr.Logger
108
122
}
109
123
110
124
func newRunnableGroup (baseContext BaseContextFunc , errChan chan error ) * runnableGroup {
@@ -113,12 +127,19 @@ func newRunnableGroup(baseContext BaseContextFunc, errChan chan error) *runnable
113
127
errChan : errChan ,
114
128
ch : make (chan * readyRunnable ),
115
129
wg : new (sync.WaitGroup ),
130
+ logger : logr .Discard (), // Default to no-op logger
116
131
}
117
132
118
133
r .ctx , r .cancel = context .WithCancel (baseContext ())
119
134
return r
120
135
}
121
136
137
+ // withLogger sets the logger for this runnable group.
138
+ func (r * runnableGroup ) withLogger (logger logr.Logger ) * runnableGroup {
139
+ r .logger = logger
140
+ return r
141
+ }
142
+
122
143
// Started returns true if the group has started.
123
144
func (r * runnableGroup ) Started () bool {
124
145
r .start .Lock ()
@@ -224,7 +245,17 @@ func (r *runnableGroup) reconcile() {
224
245
225
246
// Start the runnable.
226
247
if err := rn .Start (r .ctx ); err != nil {
227
- r .errChan <- err
248
+ // Send error with context awareness to prevent blocking during shutdown
249
+ select {
250
+ case r .errChan <- err :
251
+ // Error sent successfully
252
+ case <- r .ctx .Done ():
253
+ // Context cancelled (shutdown), drop error to prevent blocking forever
254
+ // This prevents goroutine leaks when error drain go routine has exited after timeout
255
+ if ! errors .Is (err , context .Canceled ) { // don't log context.Canceled errors as they are expected during shutdown
256
+ r .logger .Info ("error dropped during shutdown to prevent goroutine leak" , "error" , err )
257
+ }
258
+ }
228
259
}
229
260
}(runnable )
230
261
}
0 commit comments