Skip to content

Commit a49f3a4

Browse files
committed
Rewrite Warmup to avoid polling.
1 parent 1987b54 commit a49f3a4

File tree

1 file changed

+32
-47
lines changed

1 file changed

+32
-47
lines changed

pkg/internal/controller/controller.go

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
4842
type 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.
464449
func ReconcileIDFromContext(ctx context.Context) types.UID {
465450
r, ok := ctx.Value(reconcileIDKey{}).(types.UID)

0 commit comments

Comments
 (0)