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  }