You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

203 lines
4.6 KiB

package service
import (
"context"
"errors"
"sync"
"github.com/tendermint/tendermint/libs/log"
)
var (
// ErrAlreadyStarted is returned when somebody tries to start an already
// running service.
ErrAlreadyStarted = errors.New("already started")
// ErrAlreadyStopped is returned when somebody tries to stop an already
// stopped service (without resetting it).
ErrAlreadyStopped = errors.New("already stopped")
// ErrNotStarted is returned when somebody tries to stop a not running
// service.
ErrNotStarted = errors.New("not started")
)
// Service defines a service that can be started, stopped, and reset.
type Service interface {
// Start is called to start the service, which should run until
// the context terminates. If the service is already running, Start
// must report an error.
Start(context.Context) error
// Return true if the service is running
IsRunning() bool
// Wait blocks until the service is stopped.
Wait()
}
// Implementation describes the implementation that the
// BaseService implementation wraps.
type Implementation interface {
// Called by the Services Start Method
OnStart(context.Context) error
// Called when the service's context is canceled.
OnStop()
}
/*
Classical-inheritance-style service declarations. Services can be started, then
stopped, then optionally restarted.
Users can override the OnStart/OnStop methods. In the absence of errors, these
methods are guaranteed to be called at most once. If OnStart returns an error,
service won't be marked as started, so the user can call Start again.
It is safe, but an error, to call Stop without calling Start first.
Typical usage:
type FooService struct {
BaseService
// private fields
}
func NewFooService() *FooService {
fs := &FooService{
// init
}
fs.BaseService = *NewBaseService(log, "FooService", fs)
return fs
}
func (fs *FooService) OnStart(ctx context.Context) error {
// initialize private fields
// start subroutines, etc.
}
func (fs *FooService) OnStop() error {
// close/destroy private fields
// stop subroutines, etc.
}
*/
type BaseService struct {
logger log.Logger
name string
mtx sync.Mutex
quit <-chan (struct{})
cancel context.CancelFunc
// The "subclass" of BaseService
impl Implementation
}
// NewBaseService creates a new BaseService.
func NewBaseService(logger log.Logger, name string, impl Implementation) *BaseService {
return &BaseService{
logger: logger,
name: name,
impl: impl,
}
}
// Start starts the Service and calls its OnStart method. An error will be
// returned if the service is already running or stopped. To restart a
// stopped service, call Reset.
func (bs *BaseService) Start(ctx context.Context) error {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit != nil {
return ErrAlreadyStarted
}
select {
case <-bs.quit:
return ErrAlreadyStopped
default:
bs.logger.Info("starting service", "service", bs.name, "impl", bs.name)
if err := bs.impl.OnStart(ctx); err != nil {
return err
}
// we need a separate context to ensure that we start
// a thread that will get cleaned up and that the
// Stop/Wait functions work as expected.
srvCtx, cancel := context.WithCancel(context.Background())
bs.cancel = cancel
bs.quit = srvCtx.Done()
go func(ctx context.Context) {
select {
case <-srvCtx.Done():
// this means stop was called manually
return
case <-ctx.Done():
_ = bs.Stop()
}
bs.logger.Info("stopped service",
"service", bs.name)
}(ctx)
return nil
}
}
// Stop implements Service by calling OnStop (if defined) and closing quit
// channel. An error will be returned if the service is already stopped.
func (bs *BaseService) Stop() error {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit == nil {
return ErrNotStarted
}
select {
case <-bs.quit:
return ErrAlreadyStopped
default:
bs.logger.Info("stopping service", "service", bs.name)
bs.impl.OnStop()
bs.cancel()
return nil
}
}
// IsRunning implements Service by returning true or false depending on the
// service's state.
func (bs *BaseService) IsRunning() bool {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit == nil {
return false
}
select {
case <-bs.quit:
return false
default:
return true
}
}
func (bs *BaseService) getWait() <-chan struct{} {
bs.mtx.Lock()
defer bs.mtx.Unlock()
if bs.quit == nil {
out := make(chan struct{})
close(out)
return out
}
return bs.quit
}
// Wait blocks until the service is stopped.
func (bs *BaseService) Wait() { <-bs.getWait() }
// String implements Service by returning a string representation of the service.
func (bs *BaseService) String() string { return bs.name }