@@ -15,6 +15,10 @@ import (
1515 "k8s.io/client-go/tools/cache"
1616)
1717
18+ const (
19+ FactoryShutdownTimeout = 30 * time .Second
20+ )
21+
1822var (
1923 DefaultFactoryStore * FactoryStore
2024 DefaultSyncTime = 100 * time .Millisecond
@@ -36,38 +40,46 @@ type Factory struct {
3640 handlerRegistrations map [string ]cache.ResourceEventHandlerRegistration
3741 ctx context.Context
3842 cancel context.CancelFunc
43+ // done is closed when the underlying informer.Run returns
44+ done chan struct {}
3945}
4046
4147type FactoryStore struct {
42- mu sync.Mutex
43- data map [FactoryIndex ]Factory
48+ mu sync.Mutex
49+ data map [FactoryIndex ]* Factory
50+ stoppedCh map [FactoryIndex ]chan struct {}
4451}
4552
4653func NewFactoryStore () * FactoryStore {
47- return & FactoryStore {
48- data : make (map [FactoryIndex ]Factory ),
54+ fs := & FactoryStore {
55+ data : make (map [FactoryIndex ]* Factory ),
56+ stoppedCh : make (map [FactoryIndex ]chan struct {}),
4957 }
58+ return fs
5059}
5160
5261func (c * FactoryStore ) Reset () {
5362 c .mu .Lock ()
5463 defer c .mu .Unlock ()
55- c .data = make (map [FactoryIndex ]Factory )
64+ c .data = make (map [FactoryIndex ]* Factory )
65+ c .stoppedCh = make (map [FactoryIndex ]chan struct {})
5666}
5767
58- func (c * FactoryStore ) add (index FactoryIndex , f dynamicinformer.DynamicSharedInformerFactory ) {
59- ctx , cancel := context .WithCancel (context . Background () )
60- c .data [index ] = Factory {
68+ func (c * FactoryStore ) add (ctx context. Context , index FactoryIndex , f dynamicinformer.DynamicSharedInformerFactory ) {
69+ ctx , cancel := context .WithCancel (ctx )
70+ c .data [index ] = & Factory {
6171 shared : f ,
6272 handlerRegistrations : make (map [string ]cache.ResourceEventHandlerRegistration ),
6373 ctx : ctx ,
6474 cancel : cancel ,
75+ done : nil ,
6576 }
77+
6678 log .Debug ("Factory store: added a new factory for index" ,
6779 slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
6880}
6981
70- func (c * FactoryStore ) get (client dynamic.Interface , index FactoryIndex ) Factory {
82+ func (c * FactoryStore ) get (ctx context. Context , client dynamic.Interface , index FactoryIndex ) * Factory {
7183 f , ok := c .data [index ]
7284 if ok {
7385 log .Debug ("Factory store: the factory with index found" ,
@@ -91,51 +103,68 @@ func (c *FactoryStore) get(client dynamic.Interface, index FactoryIndex) Factory
91103 client , resyncPeriod , index .Namespace , tweakListOptions )
92104 factory .ForResource (index .GVR )
93105
94- c .add (index , factory )
106+ c .add (ctx , index , factory )
107+
95108 return c .data [index ]
96109}
97110
98111func (c * FactoryStore ) Start (ctx context.Context , informerId string , client dynamic.Interface , index FactoryIndex , handler cache.ResourceEventHandler , errorHandler * WatchErrorHandler ) error {
99112 c .mu .Lock ()
100113 defer c .mu .Unlock ()
101114
102- factory := c .get (client , index )
115+ factory := c .get (ctx , client , index )
103116
104117 informer := factory .shared .ForResource (index .GVR ).Informer ()
105118 // Add error handler, ignore "already started" error.
106119 _ = informer .SetWatchErrorHandler (errorHandler .handler )
120+
107121 registration , err := informer .AddEventHandler (handler )
108122 if err != nil {
109123 log .Warn ("Factory store: couldn't add event handler to the factory's informer" ,
110124 slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()),
111125 log .Err (err ))
112126 }
127+
113128 factory .handlerRegistrations [informerId ] = registration
129+
114130 log .Debug ("Factory store: increased usage counter of the factory" ,
115131 slog .Int ("value" , len (factory .handlerRegistrations )),
116132 slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
117133
118- if ! informer .HasSynced () {
119- go informer .Run (factory .ctx .Done ())
134+ // Ensure informer.Run is started once and tracked
135+ if factory .done == nil {
136+ factory .done = make (chan struct {})
137+
138+ go func () {
139+ informer .Run (factory .ctx .Done ())
120140
141+ close (factory .done )
142+
143+ log .Debug ("Factory store: informer goroutine exited" ,
144+ slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
145+ }()
146+ }
147+
148+ if ! informer .HasSynced () {
121149 if err := wait .PollUntilContextCancel (ctx , DefaultSyncTime , true , func (_ context.Context ) (bool , error ) {
122150 return informer .HasSynced (), nil
123151 }); err != nil {
124152 return err
125153 }
126154 }
155+
127156 log .Debug ("Factory store: started informer" ,
128157 slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
158+
129159 return nil
130160}
131161
132162func (c * FactoryStore ) Stop (informerId string , index FactoryIndex ) {
133163 c .mu .Lock ()
134- defer c .mu .Unlock ()
135-
136164 f , ok := c .data [index ]
137165 if ! ok {
138166 // already deleted
167+ c .mu .Unlock ()
139168 return
140169 }
141170
@@ -146,15 +175,65 @@ func (c *FactoryStore) Stop(informerId string, index FactoryIndex) {
146175 slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()),
147176 log .Err (err ))
148177 }
178+
149179 delete (f .handlerRegistrations , informerId )
180+
150181 log .Debug ("Factory store: decreased usage counter of the factory" ,
151182 slog .Int ("value" , len (f .handlerRegistrations )),
152183 slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
184+
153185 if len (f .handlerRegistrations ) == 0 {
186+ log .Debug ("Factory store: last handler removed, canceling shared informer" ,
187+ slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
188+
189+ done := f .done
190+
154191 f .cancel ()
192+ c .mu .Unlock ()
193+ if done != nil {
194+ <- done
195+ }
196+
197+ c .mu .Lock ()
155198 delete (c .data , index )
199+
156200 log .Debug ("Factory store: deleted factory" ,
157201 slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
202+
203+ if ch , ok := c .stoppedCh [index ]; ok {
204+ close (ch )
205+ delete (c .stoppedCh , index )
206+ }
207+ }
208+ }
209+
210+ c .mu .Unlock ()
211+ }
212+
213+ // WaitStopped blocks until there is no factory for the index or timeout
214+ func (c * FactoryStore ) WaitStopped (index FactoryIndex ) {
215+ c .mu .Lock ()
216+
217+ if _ , ok := c .data [index ]; ! ok {
218+ c .mu .Unlock ()
219+ return
220+ }
221+
222+ ch , ok := c .stoppedCh [index ]
223+ if ! ok {
224+ ch = make (chan struct {})
225+ c .stoppedCh [index ] = ch
226+ }
227+
228+ c .mu .Unlock ()
229+
230+ for {
231+ select {
232+ case <- ch :
233+ return
234+ case <- time .After (FactoryShutdownTimeout ):
235+ log .Warn ("timeout waiting for factory to stop" ,
236+ slog .String ("namespace" , index .Namespace ), slog .String ("gvr" , index .GVR .String ()))
158237 }
159238 }
160239}
0 commit comments