@@ -30,7 +30,6 @@ import (
3030 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3131 "k8s.io/apimachinery/pkg/util/uuid"
3232 "k8s.io/client-go/util/workqueue"
33- "k8s.io/utils/ptr"
3433
3534 "sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
3635 ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
@@ -39,11 +38,6 @@ import (
3938 "sigs.k8s.io/controller-runtime/pkg/source"
4039)
4140
42- const (
43- // syncedPollPeriod is the period to poll for cache sync
44- syncedPollPeriod = 100 * time .Millisecond
45- )
46-
4741// Controller implements controller.Controller.
4842type Controller [request comparable ] struct {
4943 // 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 {
9286 // didStartEventSourcesOnce is used to ensure that the event sources are only started once.
9387 didStartEventSourcesOnce sync.Once
9488
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
101101
102102 // LogConstructor is used to construct a logger to then log messages to users during reconciliation,
103103 // or for example when a watch is started.
@@ -171,7 +171,13 @@ func (c *Controller[request]) Warmup(ctx context.Context) error {
171171 if c .NeedWarmup == nil || ! * c .NeedWarmup {
172172 return nil
173173 }
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
175181}
176182
177183// 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 {
180186 if c .NeedWarmup == nil || ! * c .NeedWarmup {
181187 return true
182188 }
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- }
196189
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 ()
213193}
214194
215195// Start implements controller.Controller.
@@ -344,11 +324,7 @@ func (c *Controller[request]) startEventSources(ctx context.Context) error {
344324 }
345325 })
346326 }
347- err := errGroup .Wait ()
348-
349- c .didEventSourcesFinishSyncSuccessfully .Store (ptr .To (err == nil ))
350-
351- retErr = err
327+ retErr = errGroup .Wait ()
352328 })
353329
354330 return retErr
@@ -460,6 +436,15 @@ func (c *Controller[request]) updateMetrics(reconcileTime time.Duration) {
460436 ctrlmetrics .ReconcileTime .WithLabelValues (c .Name ).Observe (reconcileTime .Seconds ())
461437}
462438
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+
463448// ReconcileIDFromContext gets the reconcileID from the current context.
464449func ReconcileIDFromContext (ctx context.Context ) types.UID {
465450 r , ok := ctx .Value (reconcileIDKey {}).(types.UID )
0 commit comments