github.com/weaviate/weaviate@v1.24.6/entities/cyclemanager/cyclecallbackgroup.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package cyclemanager 13 14 import ( 15 "context" 16 "sync" 17 "time" 18 19 enterrors "github.com/weaviate/weaviate/entities/errors" 20 21 "github.com/sirupsen/logrus" 22 ) 23 24 // Container for multiple callbacks exposing CycleCallback method acting as single callback. 25 // Can be provided to CycleManager. 26 type CycleCallbackGroup interface { 27 // Adds CycleCallback method to container 28 Register(id string, cycleCallback CycleCallback, options ...RegisterOption) CycleCallbackCtrl 29 // Method of CycleCallback acting as single callback for all callbacks added to the container 30 CycleCallback(shouldAbort ShouldAbortCallback) bool 31 } 32 33 type cycleCallbackGroup struct { 34 sync.Mutex 35 36 logger logrus.FieldLogger 37 customId string 38 routinesLimit int 39 nextId uint32 40 callbackIds []uint32 41 callbacks map[uint32]*cycleCallbackMeta 42 } 43 44 func NewCallbackGroup(id string, logger logrus.FieldLogger, routinesLimit int) CycleCallbackGroup { 45 return &cycleCallbackGroup{ 46 logger: logger, 47 customId: id, 48 routinesLimit: routinesLimit, 49 nextId: 0, 50 callbackIds: []uint32{}, 51 callbacks: map[uint32]*cycleCallbackMeta{}, 52 } 53 } 54 55 func (c *cycleCallbackGroup) Register(id string, cycleCallback CycleCallback, options ...RegisterOption) CycleCallbackCtrl { 56 c.Lock() 57 defer c.Unlock() 58 59 meta := &cycleCallbackMeta{ 60 customId: id, 61 cycleCallback: cycleCallback, 62 active: true, 63 runningCtx: nil, 64 started: time.Now(), 65 intervals: nil, 66 } 67 for _, option := range options { 68 if option != nil { 69 option(meta) 70 } 71 } 72 73 callbackId := c.nextId 74 c.callbackIds = append(c.callbackIds, callbackId) 75 c.callbacks[callbackId] = meta 76 c.nextId++ 77 78 return &cycleCallbackCtrl{ 79 callbackId: callbackId, 80 callbackCustomId: id, 81 82 isActive: c.isActive, 83 activate: c.activate, 84 deactivate: c.deactivate, 85 unregister: c.unregister, 86 } 87 } 88 89 func (c *cycleCallbackGroup) CycleCallback(shouldAbort ShouldAbortCallback) bool { 90 if c.routinesLimit <= 1 { 91 return c.cycleCallbackSequential(shouldAbort) 92 } 93 return c.cycleCallbackParallel(shouldAbort, c.routinesLimit) 94 } 95 96 func (c *cycleCallbackGroup) cycleCallbackSequential(shouldAbort ShouldAbortCallback) bool { 97 anyExecuted := false 98 i := 0 99 for { 100 if shouldAbort() { 101 break 102 } 103 104 c.Lock() 105 // no more callbacks left, exit the loop 106 if i >= len(c.callbackIds) { 107 c.Unlock() 108 break 109 } 110 111 callbackId := c.callbackIds[i] 112 meta, ok := c.callbacks[callbackId] 113 // callback deleted in the meantime, remove its id 114 // and proceed to the next one (no "i" increment required) 115 if !ok { 116 c.callbackIds = append(c.callbackIds[:i], c.callbackIds[i+1:]...) 117 c.Unlock() 118 continue 119 } 120 i++ 121 // callback deactivated, proceed to the next one 122 if !meta.active { 123 c.Unlock() 124 continue 125 } 126 now := time.Now() 127 // not enough time passed since previous execution 128 if meta.intervals != nil && now.Sub(meta.started) < meta.intervals.Get() { 129 c.Unlock() 130 continue 131 } 132 // callback active, mark as running 133 runningCtx, cancel := context.WithCancel(context.Background()) 134 meta.runningCtx = runningCtx 135 meta.started = now 136 c.Unlock() 137 138 func() { 139 // cancel called in recover, regardless of panic occurred or not 140 defer c.recover(meta.customId, cancel) 141 executed := meta.cycleCallback(func() bool { 142 if shouldAbort() { 143 return true 144 } 145 146 c.Lock() 147 defer c.Unlock() 148 149 return meta.shouldAbort 150 }) 151 anyExecuted = executed || anyExecuted 152 153 if meta.intervals != nil { 154 if executed { 155 meta.intervals.Reset() 156 } else { 157 meta.intervals.Advance() 158 } 159 } 160 }() 161 } 162 163 return anyExecuted 164 } 165 166 func (c *cycleCallbackGroup) cycleCallbackParallel(shouldAbort ShouldAbortCallback, routinesLimit int) bool { 167 anyExecuted := false 168 ch := make(chan uint32) 169 lock := new(sync.Mutex) 170 wg := new(sync.WaitGroup) 171 wg.Add(routinesLimit) 172 173 i := 0 174 for r := 0; r < routinesLimit; r++ { 175 f := func() { 176 for callbackId := range ch { 177 if shouldAbort() { 178 // keep reading from channel until it is closed 179 continue 180 } 181 182 c.Lock() 183 meta, ok := c.callbacks[callbackId] 184 // callback missing or deactivated, proceed to the next one 185 if !ok || !meta.active { 186 c.Unlock() 187 continue 188 } 189 now := time.Now() 190 // not enough time passed since previous execution 191 if meta.intervals != nil && now.Sub(meta.started) < meta.intervals.Get() { 192 c.Unlock() 193 continue 194 } 195 // callback active, mark as running 196 runningCtx, cancel := context.WithCancel(context.Background()) 197 meta.runningCtx = runningCtx 198 meta.started = now 199 c.Unlock() 200 201 func() { 202 // cancel called in recover, regardless of panic occurred or not 203 defer c.recover(meta.customId, cancel) 204 executed := meta.cycleCallback(func() bool { 205 if shouldAbort() { 206 return true 207 } 208 209 c.Lock() 210 defer c.Unlock() 211 212 return meta.shouldAbort 213 }) 214 215 if executed { 216 lock.Lock() 217 anyExecuted = true 218 lock.Unlock() 219 } 220 if meta.intervals != nil { 221 if executed { 222 meta.intervals.Reset() 223 } else { 224 meta.intervals.Advance() 225 } 226 } 227 }() 228 } 229 wg.Done() 230 } 231 enterrors.GoWrapper(f, c.logger) 232 } 233 234 for { 235 if shouldAbort() { 236 close(ch) 237 break 238 } 239 240 c.Lock() 241 // no more callbacks left, exit the loop 242 if i >= len(c.callbackIds) { 243 c.Unlock() 244 close(ch) 245 break 246 } 247 248 callbackId := c.callbackIds[i] 249 _, ok := c.callbacks[callbackId] 250 // callback deleted in the meantime, remove its id 251 // and proceed to the next one (no "i" increment required) 252 if !ok { 253 c.callbackIds = append(c.callbackIds[:i], c.callbackIds[i+1:]...) 254 c.Unlock() 255 continue 256 } 257 c.Unlock() 258 ch <- callbackId 259 i++ 260 } 261 262 wg.Wait() 263 return anyExecuted 264 } 265 266 func (c *cycleCallbackGroup) recover(callbackCustomId string, cancel context.CancelFunc) { 267 if r := recover(); r != nil { 268 c.logger.WithFields(logrus.Fields{ 269 "action": "cyclemanager", 270 "callback_id": callbackCustomId, 271 "callbacks_id": c.customId, 272 }).Errorf("callback panic: %v", r) 273 } 274 cancel() 275 } 276 277 func (c *cycleCallbackGroup) mutateCallback(ctx context.Context, callbackId uint32, 278 onMetaNotFound func(callbackId uint32) error, 279 onMetaFound func(callbackId uint32, meta *cycleCallbackMeta, running bool) error, 280 ) error { 281 if ctx.Err() != nil { 282 return ctx.Err() 283 } 284 285 for { 286 // mutate callback in collection only if not running (not yet started of finished) 287 c.Lock() 288 meta, ok := c.callbacks[callbackId] 289 if !ok { 290 err := onMetaNotFound(callbackId) 291 c.Unlock() 292 return err 293 } 294 runningCtx := meta.runningCtx 295 running := runningCtx != nil && runningCtx.Err() == nil 296 297 if err := onMetaFound(callbackId, meta, running); err != nil { 298 c.Unlock() 299 return err 300 } 301 if !running { 302 c.Unlock() 303 return nil 304 } 305 c.Unlock() 306 307 // wait for callback to finish 308 select { 309 case <-runningCtx.Done(): 310 // get back to the beginning of the loop to make sure state.runningCtx 311 // was not changed. If not, loop will finish on runningCtx.Err() != nil check 312 continue 313 case <-ctx.Done(): 314 // in case both contexts are ready, but input ctx was selected 315 // check again running ctx as priority one 316 if runningCtx.Err() != nil { 317 // get back to the beginning of the loop to make sure state.runningCtx 318 // was not changed. If not, loop will finish on runningCtx.Err() != nil check 319 continue 320 } 321 // input ctx expired 322 return ctx.Err() 323 } 324 } 325 } 326 327 func (c *cycleCallbackGroup) unregister(ctx context.Context, callbackId uint32, callbackCustomId string) error { 328 err := c.mutateCallback(ctx, callbackId, 329 func(callbackId uint32) error { 330 return nil 331 }, 332 func(callbackId uint32, meta *cycleCallbackMeta, running bool) error { 333 meta.shouldAbort = true 334 if !running { 335 meta.active = false 336 delete(c.callbacks, callbackId) 337 } 338 return nil 339 }, 340 ) 341 return errorUnregisterCallback(callbackCustomId, c.customId, err) 342 } 343 344 func (c *cycleCallbackGroup) deactivate(ctx context.Context, callbackId uint32, callbackCustomId string) error { 345 err := c.mutateCallback(ctx, callbackId, 346 func(callbackId uint32) error { 347 return ErrorCallbackNotFound 348 }, 349 func(callbackId uint32, meta *cycleCallbackMeta, running bool) error { 350 meta.shouldAbort = true 351 if !running { 352 meta.active = false 353 } 354 return nil 355 }, 356 ) 357 return errorDeactivateCallback(callbackCustomId, c.customId, err) 358 } 359 360 func (c *cycleCallbackGroup) activate(callbackId uint32, callbackCustomId string) error { 361 c.Lock() 362 defer c.Unlock() 363 364 meta, ok := c.callbacks[callbackId] 365 if !ok { 366 return errorActivateCallback(callbackCustomId, c.customId, ErrorCallbackNotFound) 367 } 368 369 meta.shouldAbort = false 370 meta.active = true 371 return nil 372 } 373 374 func (c *cycleCallbackGroup) isActive(callbackId uint32, callbackCustomId string) bool { 375 c.Lock() 376 defer c.Unlock() 377 378 if meta, ok := c.callbacks[callbackId]; ok { 379 return meta.active 380 } 381 return false 382 } 383 384 type cycleCallbackMeta struct { 385 customId string 386 cycleCallback CycleCallback 387 active bool 388 // indicates whether callback is already running - context active 389 // or not running (already finished) - context expired 390 // or not running (not yet started) - context nil 391 runningCtx context.Context 392 started time.Time 393 intervals CycleIntervals 394 // true if deactivate or unregister were requested to abort callback when running 395 shouldAbort bool 396 } 397 398 type cycleCallbackGroupNoop struct{} 399 400 func NewCallbackGroupNoop() CycleCallbackGroup { 401 return &cycleCallbackGroupNoop{} 402 } 403 404 func (c *cycleCallbackGroupNoop) Register(id string, cycleCallback CycleCallback, options ...RegisterOption) CycleCallbackCtrl { 405 return NewCallbackCtrlNoop() 406 } 407 408 func (c *cycleCallbackGroupNoop) CycleCallback(shouldAbort ShouldAbortCallback) bool { 409 return false 410 } 411 412 type RegisterOption func(meta *cycleCallbackMeta) 413 414 func AsInactive() RegisterOption { 415 return func(meta *cycleCallbackMeta) { 416 meta.active = false 417 } 418 } 419 420 func WithIntervals(intervals CycleIntervals) RegisterOption { 421 if intervals == nil { 422 return nil 423 } 424 return func(meta *cycleCallbackMeta) { 425 meta.intervals = intervals 426 // adjusts start time to allow for immediate callback execution without 427 // having to wait for interval duration to pass 428 meta.started = time.Now().Add(-intervals.Get()) 429 } 430 }