Skip to content

Commit eb6f3e2

Browse files
inteonFillZpp
andcommitted
Support shutdown watches dynamically
Co-authored-by: FillZpp <[email protected]> Signed-off-by: Tim Ramlot <[email protected]>
1 parent 1b80b96 commit eb6f3e2

File tree

12 files changed

+1157
-266
lines changed

12 files changed

+1157
-266
lines changed

pkg/controller/controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ var _ = Describe("controller.Controller", func() {
7777

7878
ctx, cancel := context.WithCancel(context.Background())
7979
watchChan := make(chan event.GenericEvent, 1)
80-
watch := &source.Channel{Source: watchChan}
80+
watch := &source.Channel{Broadcaster: source.NewChannelBroadcaster(watchChan)}
8181
watchChan <- event.GenericEvent{Object: &corev1.Pod{}}
8282

8383
reconcileStarted := make(chan struct{})

pkg/controller/controllertest/util.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package controllertest
1818

1919
import (
20+
"fmt"
21+
"sync"
2022
"time"
2123

2224
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -33,7 +35,8 @@ type FakeInformer struct {
3335
// RunCount is incremented each time RunInformersAndControllers is called
3436
RunCount int
3537

36-
handlers []eventHandlerWrapper
38+
mu sync.RWMutex
39+
handlers []*eventHandlerWrapper
3740
}
3841

3942
type modernResourceEventHandler interface {
@@ -51,7 +54,8 @@ type legacyResourceEventHandler interface {
5154
// eventHandlerWrapper wraps a ResourceEventHandler in a manner that is compatible with client-go 1.27+ and older.
5255
// The interface was changed in these versions.
5356
type eventHandlerWrapper struct {
54-
handler any
57+
handler any
58+
hasSynced bool
5559
}
5660

5761
func (e eventHandlerWrapper) OnAdd(obj interface{}) {
@@ -78,6 +82,10 @@ func (e eventHandlerWrapper) OnDelete(obj interface{}) {
7882
e.handler.(legacyResourceEventHandler).OnDelete(obj)
7983
}
8084

85+
func (e eventHandlerWrapper) HasSynced() bool {
86+
return e.hasSynced
87+
}
88+
8189
// AddIndexers does nothing. TODO(community): Implement this.
8290
func (f *FakeInformer) AddIndexers(indexers cache.Indexers) error {
8391
return nil
@@ -98,10 +106,13 @@ func (f *FakeInformer) HasSynced() bool {
98106
return f.Synced
99107
}
100108

101-
// AddEventHandler implements the Informer interface. Adds an EventHandler to the fake Informers. TODO(community): Implement Registration.
109+
// AddEventHandler implements the Informer interface. Adds an EventHandler to the fake Informers.
102110
func (f *FakeInformer) AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) {
103-
f.handlers = append(f.handlers, eventHandlerWrapper{handler})
104-
return nil, nil
111+
f.mu.Lock()
112+
defer f.mu.Unlock()
113+
eh := &eventHandlerWrapper{handler, true}
114+
f.handlers = append(f.handlers, eh)
115+
return eh, nil
105116
}
106117

107118
// Run implements the Informer interface. Increments f.RunCount.
@@ -111,20 +122,26 @@ func (f *FakeInformer) Run(<-chan struct{}) {
111122

112123
// Add fakes an Add event for obj.
113124
func (f *FakeInformer) Add(obj metav1.Object) {
125+
f.mu.RLock()
126+
defer f.mu.RUnlock()
114127
for _, h := range f.handlers {
115128
h.OnAdd(obj)
116129
}
117130
}
118131

119132
// Update fakes an Update event for obj.
120133
func (f *FakeInformer) Update(oldObj, newObj metav1.Object) {
134+
f.mu.RLock()
135+
defer f.mu.RUnlock()
121136
for _, h := range f.handlers {
122137
h.OnUpdate(oldObj, newObj)
123138
}
124139
}
125140

126141
// Delete fakes an Delete event for obj.
127142
func (f *FakeInformer) Delete(obj metav1.Object) {
143+
f.mu.RLock()
144+
defer f.mu.RUnlock()
128145
for _, h := range f.handlers {
129146
h.OnDelete(obj)
130147
}
@@ -135,8 +152,25 @@ func (f *FakeInformer) AddEventHandlerWithResyncPeriod(handler cache.ResourceEve
135152
return nil, nil
136153
}
137154

138-
// RemoveEventHandler does nothing. TODO(community): Implement this.
155+
// RemoveEventHandler removes an EventHandler to the fake Informers.
139156
func (f *FakeInformer) RemoveEventHandler(handle cache.ResourceEventHandlerRegistration) error {
157+
eh, ok := handle.(*eventHandlerWrapper)
158+
if !ok {
159+
return fmt.Errorf("invalid registration type %t", handle)
160+
}
161+
162+
f.mu.Lock()
163+
defer f.mu.Unlock()
164+
165+
handlers := make([]*eventHandlerWrapper, 0, len(f.handlers))
166+
for _, h := range f.handlers {
167+
if h == eh {
168+
continue
169+
}
170+
handlers = append(handlers, h)
171+
}
172+
f.handlers = handlers
173+
140174
return nil
141175
}
142176

pkg/internal/controller/controller.go

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,18 @@ type Controller struct {
5959
// the Queue for processing
6060
Queue workqueue.RateLimitingInterface
6161

62+
// startedSources maintains a list of sources that have already started.
63+
startedSources []source.Source
64+
6265
// mu is used to synchronize Controller setup
6366
mu sync.Mutex
6467

6568
// Started is true if the Controller has been Started
6669
Started bool
6770

71+
// Stopped is true if the Controller has been Stopped
72+
Stopped bool
73+
6874
// ctx is the context that was passed to Start() and used when starting watches.
6975
//
7076
// According to the docs, contexts should not be stored in a struct: https://golang.org/pkg/context,
@@ -124,6 +130,10 @@ func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prc
124130
c.mu.Lock()
125131
defer c.mu.Unlock()
126132

133+
if c.Stopped {
134+
return fmt.Errorf("can not start watch in a stopped controller")
135+
}
136+
127137
// Controller hasn't started yet, store the watches locally and return.
128138
//
129139
// These watches are going to be held on the controller struct until the manager or user calls Start(...).
@@ -133,7 +143,11 @@ func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prc
133143
}
134144

135145
c.LogConstructor(nil).Info("Starting EventSource", "source", src)
136-
return src.Start(c.ctx, evthdler, c.Queue, prct...)
146+
err := src.Start(c.ctx, evthdler, c.Queue, prct...)
147+
if err == nil {
148+
c.startedSources = append(c.startedSources, src)
149+
}
150+
return err
137151
}
138152

139153
// NeedLeaderElection implements the manager.LeaderElectionRunnable interface.
@@ -149,22 +163,43 @@ func (c *Controller) Start(ctx context.Context) error {
149163
// use an IIFE to get proper lock handling
150164
// but lock outside to get proper handling of the queue shutdown
151165
c.mu.Lock()
166+
if c.Stopped {
167+
return fmt.Errorf("can not restart a stopped controller, you should create a new one")
168+
}
152169
if c.Started {
153170
return errors.New("controller was started more than once. This is likely to be caused by being added to a manager multiple times")
154171
}
155172

156173
c.initMetrics()
157174

158175
// Set the internal context.
159-
c.ctx = ctx
176+
var cancel context.CancelFunc
177+
c.ctx, cancel = context.WithCancel(ctx)
178+
179+
wg := &sync.WaitGroup{}
160180

161181
c.Queue = c.MakeQueue()
162-
go func() {
163-
<-ctx.Done()
164-
c.Queue.ShutDown()
182+
defer func() {
183+
var startedSources []source.Source
184+
c.mu.Lock()
185+
c.Stopped = true
186+
startedSources = c.startedSources
187+
c.mu.Unlock()
188+
189+
c.Queue.ShutDown() // Stop receiving new items in the queue
190+
191+
cancel() // cancel the context to stop all the sources
192+
for _, src := range startedSources {
193+
if err := src.Shutdown(); err != nil {
194+
c.LogConstructor(nil).Error(err, "Failed to stop watch source when controller stopping", "source", src)
195+
}
196+
}
197+
c.LogConstructor(nil).Info("All watch sources finished")
198+
199+
wg.Wait() // Wait for workers to finish
200+
c.LogConstructor(nil).Info("All workers finished")
165201
}()
166202

167-
wg := &sync.WaitGroup{}
168203
err := func() error {
169204
defer c.mu.Unlock()
170205

@@ -180,6 +215,7 @@ func (c *Controller) Start(ctx context.Context) error {
180215
if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil {
181216
return err
182217
}
218+
c.startedSources = append(c.startedSources, watch.src)
183219
}
184220

185221
// Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches
@@ -238,8 +274,6 @@ func (c *Controller) Start(ctx context.Context) error {
238274

239275
<-ctx.Done()
240276
c.LogConstructor(nil).Info("Shutdown signal received, waiting for all workers to finish")
241-
wg.Wait()
242-
c.LogConstructor(nil).Info("All workers finished")
243277
return nil
244278
}
245279

pkg/internal/controller/controller_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ var _ = Describe("controller", func() {
151151

152152
err = ctrl.Start(context.TODO())
153153
Expect(err).To(HaveOccurred())
154-
Expect(err.Error()).To(ContainSubstring("failed to wait for testcontroller caches to sync: timed out waiting for cache to be synced"))
154+
Expect(err.Error()).To(ContainSubstring("timed out trying to get an informer from cache and waiting for cache to be synced for Kind *v1.Deployment"))
155155
})
156156

157157
It("should not error when context cancelled", func() {
@@ -226,7 +226,7 @@ var _ = Describe("controller", func() {
226226
Object: p,
227227
}
228228

229-
ins := &source.Channel{Source: ch}
229+
ins := &source.Channel{Broadcaster: source.NewChannelBroadcaster(ch)}
230230
ins.DestBufferSize = 1
231231

232232
// send the event to the channel
@@ -260,7 +260,7 @@ var _ = Describe("controller", func() {
260260

261261
e := ctrl.Start(ctx)
262262
Expect(e).To(HaveOccurred())
263-
Expect(e.Error()).To(ContainSubstring("must specify Channel.Source"))
263+
Expect(e.Error()).To(ContainSubstring("must create Channel with a non-nil Broadcaster"))
264264
})
265265

266266
It("should call Start on sources with the appropriate EventHandler, Queue, and Predicates", func() {
@@ -308,7 +308,7 @@ var _ = Describe("controller", func() {
308308
Expect(ctrl.Start(ctx)).To(Succeed())
309309
err := ctrl.Start(ctx)
310310
Expect(err).To(HaveOccurred())
311-
Expect(err.Error()).To(Equal("controller was started more than once. This is likely to be caused by being added to a manager multiple times"))
311+
Expect(err.Error()).To(Equal("can not restart a stopped controller, you should create a new one"))
312312
})
313313

314314
})

0 commit comments

Comments
 (0)