@@ -30,7 +30,6 @@ import (
30
30
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
31
31
"k8s.io/apimachinery/pkg/util/uuid"
32
32
"k8s.io/client-go/util/workqueue"
33
- "k8s.io/utils/ptr"
34
33
35
34
"sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
36
35
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
@@ -39,11 +38,6 @@ import (
39
38
"sigs.k8s.io/controller-runtime/pkg/source"
40
39
)
41
40
42
- const (
43
- // syncedPollPeriod is the period to poll for cache sync
44
- syncedPollPeriod = 100 * time .Millisecond
45
- )
46
-
47
41
// Controller implements controller.Controller.
48
42
type Controller [request comparable ] struct {
49
43
// Name is used to uniquely identify a Controller in tracing, logging and monitoring. Name is required.
@@ -92,12 +86,18 @@ type Controller[request comparable] struct {
92
86
// didStartEventSourcesOnce is used to ensure that the event sources are only started once.
93
87
didStartEventSourcesOnce sync.Once
94
88
95
- // didEventSourcesFinishSyncSuccessfully is used to indicate whether the event sources have finished
96
- // successfully. It stores a *bool where
97
- // - nil: not finished syncing
98
- // - true: finished syncing without error
99
- // - false: finished syncing with error
100
- didEventSourcesFinishSyncSuccessfully atomic.Value
89
+ // ensureDidWarmupFinishChanInitializedOnce is used to ensure that the didWarmupFinishChan is
90
+ // initialized to a non-nil channel.
91
+ ensureDidWarmupFinishChanInitializedOnce sync.Once
92
+
93
+ // didWarmupFinish is closed when startEventSources returns. It is used to
94
+ // signal to WaitForWarmupComplete that the event sources have finished syncing.
95
+ didWarmupFinishChan chan struct {}
96
+
97
+ // didWarmupFinishSuccessfully is used to indicate whether the event sources have finished
98
+ // successfully. If true, the event sources have finished syncing without error. If false, the
99
+ // event sources have finished syncing but with error.
100
+ didWarmupFinishSuccessfully atomic.Bool
101
101
102
102
// LogConstructor is used to construct a logger to then log messages to users during reconciliation,
103
103
// or for example when a watch is started.
@@ -171,7 +171,13 @@ func (c *Controller[request]) Warmup(ctx context.Context) error {
171
171
if c .NeedWarmup == nil || ! * c .NeedWarmup {
172
172
return nil
173
173
}
174
- return c .startEventSources (ctx )
174
+
175
+ c .ensureDidWarmupFinishChanInitialized ()
176
+ err := c .startEventSources (ctx )
177
+ c .didWarmupFinishSuccessfully .Store (err == nil )
178
+ close (c .didWarmupFinishChan )
179
+
180
+ return err
175
181
}
176
182
177
183
// WaitForWarmupComplete returns true if warmup has completed without error, and false if there was
@@ -180,36 +186,10 @@ func (c *Controller[request]) WaitForWarmupComplete(ctx context.Context) bool {
180
186
if c .NeedWarmup == nil || ! * c .NeedWarmup {
181
187
return true
182
188
}
183
- ticker := time .NewTicker (syncedPollPeriod )
184
- defer ticker .Stop ()
185
-
186
- for {
187
- select {
188
- case <- ctx .Done ():
189
- return true
190
- case <- ticker .C :
191
- didFinishSync := c .didEventSourcesFinishSyncSuccessfully .Load ()
192
- if didFinishSync == nil {
193
- // event source still syncing
194
- continue
195
- }
196
189
197
- // This *bool assertion is done after checking for nil because type asserting a nil
198
- // interface as a *bool will return false, which is not what we want since nil should be
199
- // treated as not finished syncing.
200
- didFinishSyncPtr , ok := didFinishSync .(* bool )
201
- if ! ok {
202
- // programming error, should never happen
203
- return false
204
- }
205
-
206
- if didFinishSyncPtr != nil && * didFinishSyncPtr {
207
- // event sources finished syncing successfully
208
- return true
209
- }
210
- return false
211
- }
212
- }
190
+ c .ensureDidWarmupFinishChanInitialized ()
191
+ <- c .didWarmupFinishChan
192
+ return c .didWarmupFinishSuccessfully .Load ()
213
193
}
214
194
215
195
// Start implements controller.Controller.
@@ -344,11 +324,7 @@ func (c *Controller[request]) startEventSources(ctx context.Context) error {
344
324
}
345
325
})
346
326
}
347
- err := errGroup .Wait ()
348
-
349
- c .didEventSourcesFinishSyncSuccessfully .Store (ptr .To (err == nil ))
350
-
351
- retErr = err
327
+ retErr = errGroup .Wait ()
352
328
})
353
329
354
330
return retErr
@@ -460,6 +436,15 @@ func (c *Controller[request]) updateMetrics(reconcileTime time.Duration) {
460
436
ctrlmetrics .ReconcileTime .WithLabelValues (c .Name ).Observe (reconcileTime .Seconds ())
461
437
}
462
438
439
+ // ensureDidWarmupFinishChanInitialized ensures that the didWarmupFinishChan is initialized. This is needed
440
+ // because controller can directly be created from other packages like controller.Controller, and
441
+ // there is no way for the caller to pass in the chan.
442
+ func (c * Controller [request ]) ensureDidWarmupFinishChanInitialized () {
443
+ c .ensureDidWarmupFinishChanInitializedOnce .Do (func () {
444
+ c .didWarmupFinishChan = make (chan struct {})
445
+ })
446
+ }
447
+
463
448
// ReconcileIDFromContext gets the reconcileID from the current context.
464
449
func ReconcileIDFromContext (ctx context.Context ) types.UID {
465
450
r , ok := ctx .Value (reconcileIDKey {}).(types.UID )
0 commit comments