github.com/thanos-io/thanos@v0.32.5/internal/cortex/util/services/basic_service.go (about) 1 // Copyright (c) The Cortex Authors. 2 // Licensed under the Apache License 2.0. 3 4 package services 5 6 import ( 7 "context" 8 "fmt" 9 "sync" 10 ) 11 12 // StartingFn is called when service enters Starting state. If StartingFn returns 13 // error, service goes into Failed state. If StartingFn returns without error, service transitions into 14 // Running state (unless context has been canceled). 15 // 16 // serviceContext is a context that is finished at latest when service enters Stopping state, but can also be finished 17 // earlier when StopAsync is called on the service. This context is derived from context passed to StartAsync method. 18 type StartingFn func(serviceContext context.Context) error 19 20 // RunningFn function is called when service enters Running state. When it returns, service will move to Stopping state. 21 // If RunningFn or Stopping return error, Service will end in Failed state, otherwise if both functions return without 22 // error, service will end in Terminated state. 23 type RunningFn func(serviceContext context.Context) error 24 25 // StoppingFn function is called when service enters Stopping state. When it returns, service moves to Terminated or Failed state, 26 // depending on whether there was any error returned from previous RunningFn (if it was called) and this StoppingFn function. If both return error, 27 // RunningFn's error will be saved as failure case for Failed state. 28 // Parameter is error from Running function, or nil if there was no error. 29 type StoppingFn func(failureCase error) error 30 31 // BasicService implements contract of Service interface, using three supplied functions: StartingFn, RunningFn and StoppingFn. 32 // When service is started, these three functions are called as service transitions to Starting, Running and Stopping state. 33 // 34 // Since they are called sequentially, they don't need to synchronize access on the state. 35 // (In other words: StartingFn happens-before RunningFn, RunningFn happens-before StoppingFn). 36 // 37 // All three functions are called at most once. If they are nil, they are not called and service transitions to the next state. 38 // 39 // Context passed to StartingFn and RunningFn function is canceled when StopAsync() is called, or service enters Stopping state. 40 // This context can be used to start additional tasks from inside StartingFn or RunningFn. 41 // Same context is available via ServiceContext() method (not part of Service interface). 42 // 43 // Possible orders of how functions are called: 44 // 45 // * 1. StartingFn -- if StartingFn returns error, no other functions are called. 46 // 47 // * 1. StartingFn, 2. StoppingFn -- StartingFn doesn't return error, but StopAsync is called while running 48 // StartingFn, or context is canceled from outside while StartingFn still runs. 49 // 50 // * 1. StartingFn, 2. RunningFn, 3. StoppingFn -- this is most common, when StartingFn doesn't return error, 51 // service is not stopped and context isn't stopped externally while running StartingFn. 52 type BasicService struct { 53 // functions only run, if they are not nil. If functions are nil, service will effectively do nothing 54 // in given state, and go to the next one without any error. 55 startFn StartingFn 56 runningFn RunningFn 57 stoppingFn StoppingFn 58 59 // everything below is protected by this mutex 60 stateMu sync.RWMutex 61 state State 62 failureCase error 63 listeners []chan func(l Listener) 64 serviceName string 65 66 // closed when state reaches Running, Terminated or Failed state 67 runningWaitersCh chan struct{} 68 // closed when state reaches Terminated or Failed state 69 terminatedWaitersCh chan struct{} 70 71 serviceContext context.Context 72 serviceCancel context.CancelFunc 73 } 74 75 func invalidServiceStateError(state, expected State) error { 76 return fmt.Errorf("invalid service state: %v, expected: %v", state, expected) 77 } 78 79 func invalidServiceStateWithFailureError(state, expected State, failure error) error { 80 return fmt.Errorf("invalid service state: %v, expected: %v, failure: %w", state, expected, failure) 81 } 82 83 // NewBasicService returns service built from three functions (using BasicService). 84 func NewBasicService(start StartingFn, run RunningFn, stop StoppingFn) *BasicService { 85 return &BasicService{ 86 startFn: start, 87 runningFn: run, 88 stoppingFn: stop, 89 state: New, 90 runningWaitersCh: make(chan struct{}), 91 terminatedWaitersCh: make(chan struct{}), 92 } 93 } 94 95 // WithName sets service name, if service is still in New state, and returns service to allow 96 // usage like NewBasicService(...).WithName("service name"). 97 func (b *BasicService) WithName(name string) *BasicService { 98 // Hold lock to make sure state doesn't change while setting service name. 99 b.stateMu.Lock() 100 defer b.stateMu.Unlock() 101 102 if b.state != New { 103 return b 104 } 105 106 b.serviceName = name 107 return b 108 } 109 110 func (b *BasicService) ServiceName() string { 111 b.stateMu.RLock() 112 defer b.stateMu.RUnlock() 113 114 return b.serviceName 115 } 116 117 // StartAsync is part of Service interface. 118 func (b *BasicService) StartAsync(parentContext context.Context) error { 119 switched, oldState := b.switchState(New, Starting, func() { 120 b.serviceContext, b.serviceCancel = context.WithCancel(parentContext) 121 b.notifyListeners(func(l Listener) { l.Starting() }, false) 122 go b.main() 123 }) 124 125 if !switched { 126 return invalidServiceStateError(oldState, New) 127 } 128 return nil 129 } 130 131 // Returns true, if state switch succeeds, false if it fails. Returned state is the state before switch. 132 // if state switching succeeds, stateFn runs with lock held. 133 func (b *BasicService) switchState(from, to State, stateFn func()) (bool, State) { 134 b.stateMu.Lock() 135 defer b.stateMu.Unlock() 136 137 if b.state != from { 138 return false, b.state 139 } 140 b.state = to 141 if stateFn != nil { 142 stateFn() 143 } 144 return true, from 145 } 146 147 func (b *BasicService) mustSwitchState(from, to State, stateFn func()) { 148 if ok, _ := b.switchState(from, to, stateFn); !ok { 149 panic("switchState failed") 150 } 151 } 152 153 // This is the "main" method, that does most of the work of service. 154 // Service is in Starting state when this method runs. 155 // Entire lifecycle of the service happens here. 156 func (b *BasicService) main() { 157 var err error 158 159 if b.startFn != nil { 160 err = b.startFn(b.serviceContext) 161 } 162 163 if err != nil { 164 b.mustSwitchState(Starting, Failed, func() { 165 b.failureCase = err 166 b.serviceCancel() // cancel the context, just in case if anything started in StartingFn is using it 167 // we will not reach Running or Terminated, notify waiters 168 close(b.runningWaitersCh) 169 close(b.terminatedWaitersCh) 170 b.notifyListeners(func(l Listener) { l.Failed(Starting, err) }, true) 171 }) 172 return 173 } 174 175 stoppingFrom := Starting 176 177 // Starting has succeeded. We should switch to Running now, but let's not do that 178 // if our context has been canceled in the meantime. 179 if err = b.serviceContext.Err(); err != nil { 180 err = nil // don't report this as a failure, it is a normal "stop" signal. 181 goto stop 182 } 183 184 // We have reached Running state 185 b.mustSwitchState(Starting, Running, func() { 186 // unblock waiters waiting for Running state 187 close(b.runningWaitersCh) 188 b.notifyListeners(func(l Listener) { l.Running() }, false) 189 }) 190 191 stoppingFrom = Running 192 if b.runningFn != nil { 193 err = b.runningFn(b.serviceContext) 194 } 195 196 stop: 197 failure := err 198 b.mustSwitchState(stoppingFrom, Stopping, func() { 199 if stoppingFrom == Starting { 200 // we will not reach Running state 201 close(b.runningWaitersCh) 202 } 203 b.notifyListeners(func(l Listener) { l.Stopping(stoppingFrom) }, false) 204 }) 205 206 // Make sure we cancel the context before running stoppingFn 207 b.serviceCancel() 208 209 if b.stoppingFn != nil { 210 err = b.stoppingFn(failure) 211 if failure == nil { 212 failure = err 213 } 214 } 215 216 if failure != nil { 217 b.mustSwitchState(Stopping, Failed, func() { 218 b.failureCase = failure 219 close(b.terminatedWaitersCh) 220 b.notifyListeners(func(l Listener) { l.Failed(Stopping, failure) }, true) 221 }) 222 } else { 223 b.mustSwitchState(Stopping, Terminated, func() { 224 close(b.terminatedWaitersCh) 225 b.notifyListeners(func(l Listener) { l.Terminated(Stopping) }, true) 226 }) 227 } 228 } 229 230 // StopAsync is part of Service interface. 231 func (b *BasicService) StopAsync() { 232 if s := b.State(); s == Stopping || s == Terminated || s == Failed { 233 // no need to do anything 234 return 235 } 236 237 terminated, _ := b.switchState(New, Terminated, func() { 238 // Service wasn't started yet, and it won't be now. 239 // Notify waiters and listeners. 240 close(b.runningWaitersCh) 241 close(b.terminatedWaitersCh) 242 b.notifyListeners(func(l Listener) { l.Terminated(New) }, true) 243 }) 244 245 if !terminated { 246 // Service is Starting or Running. Just cancel the context (it must exist, 247 // as it is created when switching from New to Starting state) 248 b.serviceCancel() 249 } 250 } 251 252 // ServiceContext returns context that this service uses internally for controlling its lifecycle. It is the same context that 253 // is passed to Starting and Running functions, and is based on context passed to the service via StartAsync. 254 // 255 // Before service enters Starting state, there is no context. This context is stopped when service enters Stopping state. 256 // 257 // This can be useful in code, that embeds BasicService and wants to provide additional methods to its clients. 258 // 259 // Example: 260 // 261 // func (s *exampleService) Send(msg string) bool { 262 // ctx := s.ServiceContext() 263 // if ctx == nil { 264 // // Service is not yet started 265 // return false 266 // } 267 // select { 268 // case s.ch <- msg: 269 // return true 270 // case <-ctx.Done(): 271 // // Service is not running anymore. 272 // return false 273 // } 274 // } 275 // 276 // This is not part of Service interface, and clients of the Service should not use it. 277 func (b *BasicService) ServiceContext() context.Context { 278 s := b.State() 279 if s == New { 280 return nil 281 } 282 // no need for locking, as we have checked the state. 283 return b.serviceContext 284 } 285 286 // AwaitRunning is part of Service interface. 287 func (b *BasicService) AwaitRunning(ctx context.Context) error { 288 return b.awaitState(ctx, Running, b.runningWaitersCh) 289 } 290 291 // AwaitTerminated is part of Service interface. 292 func (b *BasicService) AwaitTerminated(ctx context.Context) error { 293 return b.awaitState(ctx, Terminated, b.terminatedWaitersCh) 294 } 295 296 func (b *BasicService) awaitState(ctx context.Context, expectedState State, ch chan struct{}) error { 297 select { 298 case <-ctx.Done(): 299 return ctx.Err() 300 case <-ch: 301 s := b.State() 302 if s == expectedState { 303 return nil 304 } 305 306 // if service has failed, include failure case in the returned error. 307 if failure := b.FailureCase(); failure != nil { 308 return invalidServiceStateWithFailureError(s, expectedState, failure) 309 } 310 311 return invalidServiceStateError(s, expectedState) 312 } 313 } 314 315 // FailureCase is part of Service interface. 316 func (b *BasicService) FailureCase() error { 317 b.stateMu.RLock() 318 defer b.stateMu.RUnlock() 319 320 return b.failureCase 321 } 322 323 // State is part of Service interface. 324 func (b *BasicService) State() State { 325 b.stateMu.RLock() 326 defer b.stateMu.RUnlock() 327 return b.state 328 } 329 330 // AddListener is part of Service interface. 331 func (b *BasicService) AddListener(listener Listener) { 332 b.stateMu.Lock() 333 defer b.stateMu.Unlock() 334 335 if b.state == Terminated || b.state == Failed { 336 // no more state transitions will be done, and channel wouldn't get closed 337 return 338 } 339 340 // There are max 4 state transitions. We use buffer to avoid blocking the sender, 341 // which holds service lock. 342 ch := make(chan func(l Listener), 4) 343 b.listeners = append(b.listeners, ch) 344 345 // each listener has its own goroutine, processing events. 346 go func() { 347 for lfn := range ch { 348 lfn(listener) 349 } 350 }() 351 } 352 353 // lock must be held here. Read lock would be good enough, but since 354 // this is called from state transitions, full lock is used. 355 func (b *BasicService) notifyListeners(lfn func(l Listener), closeChan bool) { 356 for _, ch := range b.listeners { 357 ch <- lfn 358 if closeChan { 359 close(ch) 360 } 361 } 362 }