4
4
"context"
5
5
"errors"
6
6
"sync"
7
- "time"
8
7
9
- "k8s.io/apimachinery/pkg/util/wait"
10
8
"sigs.k8s.io/controller-runtime/pkg/webhook"
11
9
)
12
10
18
16
// a ready check.
19
17
type readyRunnable struct {
20
18
Runnable
21
- Check runnableCheck
22
- Ready bool
19
+ Check runnableCheck
20
+ signalReady bool
23
21
}
24
22
25
23
// runnableCheck can be passed to Add() to let the runnable group determine that a
@@ -46,33 +44,27 @@ func newRunnables(errChan chan error) *runnables {
46
44
}
47
45
}
48
46
49
- // Add adds a runnable and its ready check to the closest
50
- // group of runnable that they belong to.
47
+ // Add adds a runnable to closest group of runnable that they belong to.
51
48
//
52
49
// Add should be able to be called before and after Start, but not after StopAndWait.
53
50
// Add should return an error when called during StopAndWait.
54
51
// The runnables added before Start are started when Start is called.
55
52
// The runnables added after Start are started directly.
56
- func (r * runnables ) Add (fn Runnable , ready runnableCheck ) error {
57
- if ready == nil {
58
- // If we don't have a readiness check, always return true.
59
- ready = func (_ context.Context ) bool { return true }
60
- }
61
-
53
+ func (r * runnables ) Add (fn Runnable ) error {
62
54
switch runnable := fn .(type ) {
63
55
case hasCache :
64
56
return r .Caches .Add (fn , func (ctx context.Context ) bool {
65
- return ready ( ctx ) && runnable .GetCache ().WaitForCacheSync (ctx )
57
+ return runnable .GetCache ().WaitForCacheSync (ctx )
66
58
})
67
59
case * webhook.Server :
68
- return r .Webhooks .Add (fn , ready )
60
+ return r .Webhooks .Add (fn , nil )
69
61
case LeaderElectionRunnable :
70
62
if ! runnable .NeedLeaderElection () {
71
- return r .Others .Add (fn , ready )
63
+ return r .Others .Add (fn , nil )
72
64
}
73
- return r .LeaderElection .Add (fn , ready )
65
+ return r .LeaderElection .Add (fn , nil )
74
66
default :
75
- return r .LeaderElection .Add (fn , ready )
67
+ return r .LeaderElection .Add (fn , nil )
76
68
}
77
69
}
78
70
@@ -85,9 +77,11 @@ type runnableGroup struct {
85
77
ctx context.Context
86
78
cancel context.CancelFunc
87
79
88
- start sync.Mutex
89
- startOnce sync.Once
90
- started bool
80
+ start sync.Mutex
81
+ startOnce sync.Once
82
+ started bool
83
+ startQueue []* readyRunnable
84
+ startReadyCh chan * readyRunnable
91
85
92
86
stop sync.RWMutex
93
87
stopOnce sync.Once
@@ -104,23 +98,14 @@ type runnableGroup struct {
104
98
// wg is an internal sync.WaitGroup that allows us to properly stop
105
99
// and wait for all the runnables to finish before returning.
106
100
wg * sync.WaitGroup
107
-
108
- // group is a sync.Map that contains every runnable ever.
109
- // The key of the map is the runnable itself (key'd by pointer),
110
- // while the value is its ready state.
111
- //
112
- // The group of runnable is append-only, runnables scheduled
113
- // through this group are going to be stored in this internal map
114
- // until the application exits. The limit is the available memory.
115
- group * sync.Map
116
101
}
117
102
118
103
func newRunnableGroup (errChan chan error ) * runnableGroup {
119
104
r := & runnableGroup {
120
- errChan : errChan ,
121
- ch : make ( chan * readyRunnable ) ,
122
- wg : new (sync. WaitGroup ),
123
- group : new (sync.Map ),
105
+ startReadyCh : make ( chan * readyRunnable ) ,
106
+ errChan : errChan ,
107
+ ch : make ( chan * readyRunnable ),
108
+ wg : new (sync.WaitGroup ),
124
109
}
125
110
r .ctx , r .cancel = context .WithCancel (context .Background ())
126
111
return r
@@ -133,25 +118,57 @@ func (r *runnableGroup) Started() bool {
133
118
return r .started
134
119
}
135
120
136
- // StartAndWaitReady starts all the runnables previously
137
- // added to the group and waits for all to report ready.
138
- func (r * runnableGroup ) StartAndWaitReady (ctx context.Context ) error {
139
- r .Start ()
140
- return r .WaitReady (ctx )
141
- }
121
+ // Start starts the group and waits for all
122
+ // initially registered runnables to start.
123
+ // It can only be called once, subsequent calls have no effect.
124
+ func (r * runnableGroup ) Start (ctx context.Context ) error {
125
+ var retErr error
142
126
143
- // Start starts the group, it can only be called once.
144
- func (r * runnableGroup ) Start () {
145
127
r .startOnce .Do (func () {
128
+ defer close (r .startReadyCh )
129
+
130
+ // Start the internal reconciler.
146
131
go r .reconcile ()
132
+
133
+ // Start the group and queue up all
134
+ // the runnables that were added prior.
147
135
r .start .Lock ()
148
136
r .started = true
149
- r . group . Range ( func ( key , _ interface {}) bool {
150
- r . ch <- key .( * readyRunnable )
151
- return true
152
- })
137
+ for _ , rn := range r . startQueue {
138
+ rn . signalReady = true
139
+ r . ch <- rn
140
+ }
153
141
r .start .Unlock ()
142
+
143
+ // If we don't have any queue, return.
144
+ if len (r .startQueue ) == 0 {
145
+ return
146
+ }
147
+
148
+ // Wait for all runnables to signal.
149
+ for {
150
+ select {
151
+ case <- ctx .Done ():
152
+ if err := ctx .Err (); ! errors .Is (err , context .Canceled ) {
153
+ retErr = err
154
+ }
155
+ case rn := <- r .startReadyCh :
156
+ for i , existing := range r .startQueue {
157
+ if existing == rn {
158
+ // Remove the item from the start queue.
159
+ r .startQueue = append (r .startQueue [:i ], r .startQueue [i + 1 :]... )
160
+ break
161
+ }
162
+ }
163
+ // We're done waiting if the queue is empty, return.
164
+ if len (r .startQueue ) == 0 {
165
+ return
166
+ }
167
+ }
168
+ }
154
169
})
170
+
171
+ return retErr
155
172
}
156
173
157
174
// reconcile is our main entrypoint for every runnable added
@@ -185,26 +202,17 @@ func (r *runnableGroup) reconcile() {
185
202
go func (rn * readyRunnable ) {
186
203
go func () {
187
204
if rn .Check (r .ctx ) {
188
- r .group .Store (rn , true )
205
+ if rn .signalReady {
206
+ r .startReadyCh <- rn
207
+ }
189
208
}
190
209
}()
191
210
192
211
// If we return, the runnable ended cleanly
193
212
// or returned an error to the channel.
194
213
//
195
- // We should always decrement the WaitGroup and
196
- // mark the runnable as ready.
197
- //
198
- // Think about the group as an append-only system.
199
- //
200
- // A runnable is marked as ready if:
201
- // - The health check return true.
202
- // - The runnable Start() method returned and
203
- // it either finished cleanly (e.g. one shot operations)
204
- // or it failed to run and it returned an error which
205
- // gets propagated to the manager.
214
+ // We should always decrement the WaitGroup here.
206
215
defer r .wg .Done ()
207
- defer r .group .Store (rn , true )
208
216
209
217
// Start the runnable.
210
218
if err := rn .Start (r .ctx ); err != nil {
@@ -214,27 +222,6 @@ func (r *runnableGroup) reconcile() {
214
222
}
215
223
}
216
224
217
- // WaitReady polls until the group is ready or until the context is cancelled.
218
- func (r * runnableGroup ) WaitReady (ctx context.Context ) error {
219
- return wait .PollImmediateInfiniteWithContext (ctx ,
220
- 100 * time .Millisecond ,
221
- func (_ context.Context ) (bool , error ) {
222
- if ! r .Started () {
223
- return false , nil
224
- }
225
- ready , total := 0 , 0
226
- r .group .Range (func (_ , value interface {}) bool {
227
- total ++
228
- if rd , ok := value .(bool ); ok && rd {
229
- ready ++
230
- }
231
- return true
232
- })
233
- return ready == total , nil
234
- },
235
- )
236
- }
237
-
238
225
// Add should be able to be called before and after Start, but not after StopAndWait.
239
226
// Add should return an error when called during StopAndWait.
240
227
func (r * runnableGroup ) Add (rn Runnable , ready runnableCheck ) error {
@@ -261,11 +248,10 @@ func (r *runnableGroup) Add(rn Runnable, ready runnableCheck) error {
261
248
{
262
249
r .start .Lock ()
263
250
264
- // Store the runnable in the internal buffer.
265
- r .group .Store (readyRunnable , false )
266
-
267
251
// Check if we're already started.
268
252
if ! r .started {
253
+ // Store the runnable in the internal if not.
254
+ r .startQueue = append (r .startQueue , readyRunnable )
269
255
r .start .Unlock ()
270
256
return nil
271
257
}
@@ -283,7 +269,7 @@ func (r *runnableGroup) StopAndWait(ctx context.Context) {
283
269
// Close the reconciler channel once we're done.
284
270
defer close (r .ch )
285
271
286
- r .Start ()
272
+ _ = r .Start (ctx )
287
273
r .stop .Lock ()
288
274
// Store the stopped variable so we don't accept any new
289
275
// runnables for the time being.
0 commit comments