github.com/evdatsion/aphelion-dpos-bft@v0.32.1/libs/common/service.go (about)

     1  package common
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync/atomic"
     7  
     8  	"github.com/evdatsion/aphelion-dpos-bft/libs/log"
     9  )
    10  
    11  var (
    12  	// ErrAlreadyStarted is returned when somebody tries to start an already
    13  	// running service.
    14  	ErrAlreadyStarted = errors.New("already started")
    15  	// ErrAlreadyStopped is returned when somebody tries to stop an already
    16  	// stopped service (without resetting it).
    17  	ErrAlreadyStopped = errors.New("already stopped")
    18  	// ErrNotStarted is returned when somebody tries to stop a not running
    19  	// service.
    20  	ErrNotStarted = errors.New("not started")
    21  )
    22  
    23  // Service defines a service that can be started, stopped, and reset.
    24  type Service interface {
    25  	// Start the service.
    26  	// If it's already started or stopped, will return an error.
    27  	// If OnStart() returns an error, it's returned by Start()
    28  	Start() error
    29  	OnStart() error
    30  
    31  	// Stop the service.
    32  	// If it's already stopped, will return an error.
    33  	// OnStop must never error.
    34  	Stop() error
    35  	OnStop()
    36  
    37  	// Reset the service.
    38  	// Panics by default - must be overwritten to enable reset.
    39  	Reset() error
    40  	OnReset() error
    41  
    42  	// Return true if the service is running
    43  	IsRunning() bool
    44  
    45  	// Quit returns a channel, which is closed once service is stopped.
    46  	Quit() <-chan struct{}
    47  
    48  	// String representation of the service
    49  	String() string
    50  
    51  	// SetLogger sets a logger.
    52  	SetLogger(log.Logger)
    53  }
    54  
    55  /*
    56  Classical-inheritance-style service declarations. Services can be started, then
    57  stopped, then optionally restarted.
    58  
    59  Users can override the OnStart/OnStop methods. In the absence of errors, these
    60  methods are guaranteed to be called at most once. If OnStart returns an error,
    61  service won't be marked as started, so the user can call Start again.
    62  
    63  A call to Reset will panic, unless OnReset is overwritten, allowing
    64  OnStart/OnStop to be called again.
    65  
    66  The caller must ensure that Start and Stop are not called concurrently.
    67  
    68  It is ok to call Stop without calling Start first.
    69  
    70  Typical usage:
    71  
    72  	type FooService struct {
    73  		BaseService
    74  		// private fields
    75  	}
    76  
    77  	func NewFooService() *FooService {
    78  		fs := &FooService{
    79  			// init
    80  		}
    81  		fs.BaseService = *NewBaseService(log, "FooService", fs)
    82  		return fs
    83  	}
    84  
    85  	func (fs *FooService) OnStart() error {
    86  		fs.BaseService.OnStart() // Always call the overridden method.
    87  		// initialize private fields
    88  		// start subroutines, etc.
    89  	}
    90  
    91  	func (fs *FooService) OnStop() error {
    92  		fs.BaseService.OnStop() // Always call the overridden method.
    93  		// close/destroy private fields
    94  		// stop subroutines, etc.
    95  	}
    96  */
    97  type BaseService struct {
    98  	Logger  log.Logger
    99  	name    string
   100  	started uint32 // atomic
   101  	stopped uint32 // atomic
   102  	quit    chan struct{}
   103  
   104  	// The "subclass" of BaseService
   105  	impl Service
   106  }
   107  
   108  // NewBaseService creates a new BaseService.
   109  func NewBaseService(logger log.Logger, name string, impl Service) *BaseService {
   110  	if logger == nil {
   111  		logger = log.NewNopLogger()
   112  	}
   113  
   114  	return &BaseService{
   115  		Logger: logger,
   116  		name:   name,
   117  		quit:   make(chan struct{}),
   118  		impl:   impl,
   119  	}
   120  }
   121  
   122  // SetLogger implements Service by setting a logger.
   123  func (bs *BaseService) SetLogger(l log.Logger) {
   124  	bs.Logger = l
   125  }
   126  
   127  // Start implements Service by calling OnStart (if defined). An error will be
   128  // returned if the service is already running or stopped. Not to start the
   129  // stopped service, you need to call Reset.
   130  func (bs *BaseService) Start() error {
   131  	if atomic.CompareAndSwapUint32(&bs.started, 0, 1) {
   132  		if atomic.LoadUint32(&bs.stopped) == 1 {
   133  			bs.Logger.Error(fmt.Sprintf("Not starting %v -- already stopped", bs.name), "impl", bs.impl)
   134  			// revert flag
   135  			atomic.StoreUint32(&bs.started, 0)
   136  			return ErrAlreadyStopped
   137  		}
   138  		bs.Logger.Info(fmt.Sprintf("Starting %v", bs.name), "impl", bs.impl)
   139  		err := bs.impl.OnStart()
   140  		if err != nil {
   141  			// revert flag
   142  			atomic.StoreUint32(&bs.started, 0)
   143  			return err
   144  		}
   145  		return nil
   146  	}
   147  	bs.Logger.Debug(fmt.Sprintf("Not starting %v -- already started", bs.name), "impl", bs.impl)
   148  	return ErrAlreadyStarted
   149  }
   150  
   151  // OnStart implements Service by doing nothing.
   152  // NOTE: Do not put anything in here,
   153  // that way users don't need to call BaseService.OnStart()
   154  func (bs *BaseService) OnStart() error { return nil }
   155  
   156  // Stop implements Service by calling OnStop (if defined) and closing quit
   157  // channel. An error will be returned if the service is already stopped.
   158  func (bs *BaseService) Stop() error {
   159  	if atomic.CompareAndSwapUint32(&bs.stopped, 0, 1) {
   160  		if atomic.LoadUint32(&bs.started) == 0 {
   161  			bs.Logger.Error(fmt.Sprintf("Not stopping %v -- have not been started yet", bs.name), "impl", bs.impl)
   162  			// revert flag
   163  			atomic.StoreUint32(&bs.stopped, 0)
   164  			return ErrNotStarted
   165  		}
   166  		bs.Logger.Info(fmt.Sprintf("Stopping %v", bs.name), "impl", bs.impl)
   167  		bs.impl.OnStop()
   168  		close(bs.quit)
   169  		return nil
   170  	}
   171  	bs.Logger.Debug(fmt.Sprintf("Stopping %v (ignoring: already stopped)", bs.name), "impl", bs.impl)
   172  	return ErrAlreadyStopped
   173  }
   174  
   175  // OnStop implements Service by doing nothing.
   176  // NOTE: Do not put anything in here,
   177  // that way users don't need to call BaseService.OnStop()
   178  func (bs *BaseService) OnStop() {}
   179  
   180  // Reset implements Service by calling OnReset callback (if defined). An error
   181  // will be returned if the service is running.
   182  func (bs *BaseService) Reset() error {
   183  	if !atomic.CompareAndSwapUint32(&bs.stopped, 1, 0) {
   184  		bs.Logger.Debug(fmt.Sprintf("Can't reset %v. Not stopped", bs.name), "impl", bs.impl)
   185  		return fmt.Errorf("can't reset running %s", bs.name)
   186  	}
   187  
   188  	// whether or not we've started, we can reset
   189  	atomic.CompareAndSwapUint32(&bs.started, 1, 0)
   190  
   191  	bs.quit = make(chan struct{})
   192  	return bs.impl.OnReset()
   193  }
   194  
   195  // OnReset implements Service by panicking.
   196  func (bs *BaseService) OnReset() error {
   197  	panic("The service cannot be reset")
   198  }
   199  
   200  // IsRunning implements Service by returning true or false depending on the
   201  // service's state.
   202  func (bs *BaseService) IsRunning() bool {
   203  	return atomic.LoadUint32(&bs.started) == 1 && atomic.LoadUint32(&bs.stopped) == 0
   204  }
   205  
   206  // Wait blocks until the service is stopped.
   207  func (bs *BaseService) Wait() {
   208  	<-bs.quit
   209  }
   210  
   211  // String implements Service by returning a string representation of the service.
   212  func (bs *BaseService) String() string {
   213  	return bs.name
   214  }
   215  
   216  // Quit Implements Service by returning a quit channel.
   217  func (bs *BaseService) Quit() <-chan struct{} {
   218  	return bs.quit
   219  }