github.com/weaviate/weaviate@v1.24.6/entities/cyclemanager/cyclemanager.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 "fmt" 17 "runtime" 18 "sync" 19 20 "github.com/sirupsen/logrus" 21 enterrors "github.com/weaviate/weaviate/entities/errors" 22 ) 23 24 var _NUMCPU = runtime.NumCPU() 25 26 type ( 27 // indicates whether cyclemanager's stop was requested to allow safely 28 // abort execution of CycleCallback and stop cyclemanager earlier 29 ShouldAbortCallback func() bool 30 // return value indicates whether actual work was done in the cycle 31 CycleCallback func(shouldAbort ShouldAbortCallback) bool 32 ) 33 34 type CycleManager interface { 35 Start() 36 Stop(ctx context.Context) chan bool 37 StopAndWait(ctx context.Context) error 38 Running() bool 39 } 40 41 type cycleManager struct { 42 sync.RWMutex 43 44 cycleCallback CycleCallback 45 cycleTicker CycleTicker 46 running bool 47 stopSignal chan struct{} 48 49 stopContexts []context.Context 50 stopResults []chan bool 51 52 logger logrus.FieldLogger 53 } 54 55 func NewManager(cycleTicker CycleTicker, cycleCallback CycleCallback, logger logrus.FieldLogger) CycleManager { 56 return &cycleManager{ 57 cycleCallback: cycleCallback, 58 cycleTicker: cycleTicker, 59 running: false, 60 stopSignal: make(chan struct{}, 1), 61 logger: logger, 62 } 63 } 64 65 // Starts instance, does not block 66 // Does nothing if instance is already started 67 func (c *cycleManager) Start() { 68 c.Lock() 69 defer c.Unlock() 70 71 if c.running { 72 return 73 } 74 75 enterrors.GoWrapper(func() { 76 c.cycleTicker.Start() 77 defer c.cycleTicker.Stop() 78 79 for { 80 if c.isStopRequested() { 81 c.Lock() 82 if c.shouldStop() { 83 c.handleStopRequest(true) 84 c.Unlock() 85 break 86 } 87 c.handleStopRequest(false) 88 c.Unlock() 89 continue 90 } 91 c.cycleTicker.CycleExecuted(c.cycleCallback(c.shouldAbortCycleCallback)) 92 } 93 }, c.logger) 94 95 c.running = true 96 } 97 98 // Stops running instance, does not block 99 // Returns channel with final stop result - true / false 100 // 101 // If given context is cancelled before it is handled by stop logic, instance is not stopped 102 // If called multiple times, all contexts have to be cancelled to cancel stop 103 // (any valid will result in stopping instance) 104 // stopResult is the same (consistent) for multiple calls 105 func (c *cycleManager) Stop(ctx context.Context) (stopResult chan bool) { 106 c.Lock() 107 defer c.Unlock() 108 109 stopResult = make(chan bool, 1) 110 if !c.running { 111 stopResult <- true 112 close(stopResult) 113 return stopResult 114 } 115 116 if len(c.stopContexts) == 0 { 117 defer func() { 118 c.stopSignal <- struct{}{} 119 }() 120 } 121 c.stopContexts = append(c.stopContexts, ctx) 122 c.stopResults = append(c.stopResults, stopResult) 123 124 return stopResult 125 } 126 127 // Stops running instance, waits for stop to occur or context to expire (which comes first) 128 // Returns error if instance was not stopped 129 func (c *cycleManager) StopAndWait(ctx context.Context) error { 130 // if both channels are ready, chan is selected randomly, therefore regardless of 131 // channel selected first, second one is also checked 132 stop := c.Stop(ctx) 133 done := ctx.Done() 134 135 select { 136 case <-done: 137 select { 138 case stopped := <-stop: 139 if !stopped { 140 return ctx.Err() 141 } 142 default: 143 return ctx.Err() 144 } 145 case stopped := <-stop: 146 if !stopped { 147 if ctx.Err() != nil { 148 return ctx.Err() 149 } 150 return fmt.Errorf("failed to stop cycle") 151 } 152 } 153 return nil 154 } 155 156 func (c *cycleManager) Running() bool { 157 c.RLock() 158 defer c.RUnlock() 159 160 return c.running 161 } 162 163 func (c *cycleManager) shouldStop() bool { 164 for _, ctx := range c.stopContexts { 165 if ctx.Err() == nil { 166 return true 167 } 168 } 169 return false 170 } 171 172 func (c *cycleManager) shouldAbortCycleCallback() bool { 173 c.RLock() 174 defer c.RUnlock() 175 176 return c.shouldStop() 177 } 178 179 func (c *cycleManager) isStopRequested() bool { 180 select { 181 case <-c.stopSignal: 182 case <-c.cycleTicker.C(): 183 // as stop chan has higher priority, 184 // it is checked again in case of ticker was selected over stop if both were ready 185 select { 186 case <-c.stopSignal: 187 default: 188 return false 189 } 190 } 191 return true 192 } 193 194 func (c *cycleManager) handleStopRequest(stopped bool) { 195 for _, stopResult := range c.stopResults { 196 stopResult <- stopped 197 close(stopResult) 198 } 199 c.running = !stopped 200 c.stopContexts = nil 201 c.stopResults = nil 202 } 203 204 func NewManagerNoop() CycleManager { 205 return &cycleManagerNoop{running: false} 206 } 207 208 type cycleManagerNoop struct { 209 running bool 210 } 211 212 func (c *cycleManagerNoop) Start() { 213 c.running = true 214 } 215 216 func (c *cycleManagerNoop) Stop(ctx context.Context) chan bool { 217 if !c.running { 218 return c.closedChan(true) 219 } 220 if ctx.Err() != nil { 221 return c.closedChan(false) 222 } 223 224 c.running = false 225 return c.closedChan(true) 226 } 227 228 func (c *cycleManagerNoop) StopAndWait(ctx context.Context) error { 229 if <-c.Stop(ctx) { 230 return nil 231 } 232 return ctx.Err() 233 } 234 235 func (c *cycleManagerNoop) Running() bool { 236 return c.running 237 } 238 239 func (c *cycleManagerNoop) closedChan(val bool) chan bool { 240 ch := make(chan bool, 1) 241 ch <- val 242 close(ch) 243 return ch 244 }