@@ -68,6 +68,11 @@ type Controller struct {
68
68
// specified, or the ~/.kube/Config.
69
69
Config * rest.Config
70
70
71
+ // MakeQueue constructs the queue for this controller once the controller is ready to start.
72
+ // This exists because the standard Kubernetes workqueues start themselves immediately, which
73
+ // leads to goroutine leaks if something calls controller.New repeatedly.
74
+ MakeQueue func () workqueue.RateLimitingInterface
75
+
71
76
// Queue is an listeningQueue that listens for events from Informers and adds object keys to
72
77
// the Queue for processing
73
78
Queue workqueue.RateLimitingInterface
@@ -93,6 +98,16 @@ type Controller struct {
93
98
Recorder record.EventRecorder
94
99
95
100
// TODO(community): Consider initializing a logger with the Controller Name as the tag
101
+
102
+ // watches maintains a list of sources, handlers, and predicates to start when the controller is started.
103
+ watches []watchDescription
104
+ }
105
+
106
+ // watchDescription contains all the information necessary to start a watch.
107
+ type watchDescription struct {
108
+ src source.Source
109
+ handler handler.EventHandler
110
+ predicates []predicate.Predicate
96
111
}
97
112
98
113
// Reconcile implements reconcile.Reconciler
@@ -118,47 +133,72 @@ func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prc
118
133
}
119
134
}
120
135
121
- log .Info ("Starting EventSource" , "controller" , c .Name , "source" , src )
122
- return src .Start (evthdler , c .Queue , prct ... )
136
+ c .watches = append (c .watches , watchDescription {src : src , handler : evthdler , predicates : prct })
137
+ if c .Started {
138
+ log .Info ("Starting EventSource" , "controller" , c .Name , "source" , src )
139
+ return src .Start (evthdler , c .Queue , prct ... )
140
+ }
141
+
142
+ return nil
123
143
}
124
144
125
145
// Start implements controller.Controller
126
146
func (c * Controller ) Start (stop <- chan struct {}) error {
147
+ // use an IIFE to get proper lock handling
148
+ // but lock outside to get proper handling of the queue shutdown
127
149
c .mu .Lock ()
128
150
129
- // TODO(pwittrock): Reconsider HandleCrash
130
- defer utilruntime .HandleCrash ()
131
- defer c .Queue .ShutDown ()
151
+ c .Queue = c .MakeQueue ()
152
+ defer c .Queue .ShutDown () // needs to be outside the iife so that we shutdown after the stop channel is closed
132
153
133
- // Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
134
- log . Info ( "Starting Controller" , "controller" , c . Name )
154
+ err := func () error {
155
+ defer c . mu . Unlock ( )
135
156
136
- // Wait for the caches to be synced before starting workers
137
- if c .WaitForCacheSync == nil {
138
- c .WaitForCacheSync = c .Cache .WaitForCacheSync
139
- }
140
- if ok := c .WaitForCacheSync (stop ); ! ok {
141
- // This code is unreachable right now since WaitForCacheSync will never return an error
142
- // Leaving it here because that could happen in the future
143
- err := fmt .Errorf ("failed to wait for %s caches to sync" , c .Name )
144
- log .Error (err , "Could not wait for Cache to sync" , "controller" , c .Name )
145
- c .mu .Unlock ()
146
- return err
147
- }
157
+ // TODO(pwittrock): Reconsider HandleCrash
158
+ defer utilruntime .HandleCrash ()
148
159
149
- if c .JitterPeriod == 0 {
150
- c .JitterPeriod = 1 * time .Second
151
- }
160
+ // NB(directxman12): launch the sources *before* trying to wait for the
161
+ // caches to sync so that they have a chance to register their intendeded
162
+ // caches.
163
+ for _ , watch := range c .watches {
164
+ log .Info ("Starting EventSource" , "controller" , c .Name , "source" , watch .src )
165
+ if err := watch .src .Start (watch .handler , c .Queue , watch .predicates ... ); err != nil {
166
+ return err
167
+ }
168
+ }
152
169
153
- // Launch workers to process resources
154
- log .Info ("Starting workers" , "controller" , c .Name , "worker count" , c .MaxConcurrentReconciles )
155
- for i := 0 ; i < c .MaxConcurrentReconciles ; i ++ {
156
- // Process work items
157
- go wait .Until (c .worker , c .JitterPeriod , stop )
158
- }
170
+ // Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
171
+ log .Info ("Starting Controller" , "controller" , c .Name )
172
+
173
+ // Wait for the caches to be synced before starting workers
174
+ if c .WaitForCacheSync == nil {
175
+ c .WaitForCacheSync = c .Cache .WaitForCacheSync
176
+ }
177
+ if ok := c .WaitForCacheSync (stop ); ! ok {
178
+ // This code is unreachable right now since WaitForCacheSync will never return an error
179
+ // Leaving it here because that could happen in the future
180
+ err := fmt .Errorf ("failed to wait for %s caches to sync" , c .Name )
181
+ log .Error (err , "Could not wait for Cache to sync" , "controller" , c .Name )
182
+ return err
183
+ }
184
+
185
+ if c .JitterPeriod == 0 {
186
+ c .JitterPeriod = 1 * time .Second
187
+ }
188
+
189
+ // Launch workers to process resources
190
+ log .Info ("Starting workers" , "controller" , c .Name , "worker count" , c .MaxConcurrentReconciles )
191
+ for i := 0 ; i < c .MaxConcurrentReconciles ; i ++ {
192
+ // Process work items
193
+ go wait .Until (c .worker , c .JitterPeriod , stop )
194
+ }
159
195
160
- c .Started = true
161
- c .mu .Unlock ()
196
+ c .Started = true
197
+ return nil
198
+ }()
199
+ if err != nil {
200
+ return err
201
+ }
162
202
163
203
<- stop
164
204
log .Info ("Stopping workers" , "controller" , c .Name )
0 commit comments