/* Classical-inheritance-style service declarations. Services can be started, then stopped, then optionally restarted. Users can override the OnStart/OnStop methods. By default, these methods are guaranteed to be called at most once. A call to Reset will panic, unless OnReset is overwritten, allowing OnStart/OnStop to be called again. Caller must ensure that Start() and Stop() are not called concurrently. It is ok to call Stop() without calling Start() first. Services cannot be re-started unless OnReset is overwritten to allow it. 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() error { fs.BaseService.OnStart() // Always call the overridden method. // initialize private fields // start subroutines, etc. } func (fs *FooService) OnStop() error { fs.BaseService.OnStop() // Always call the overridden method. // close/destroy private fields // stop subroutines, etc. } */ package common import ( "sync/atomic" "github.com/tendermint/log15" ) type Service interface { Start() (bool, error) OnStart() error Stop() bool OnStop() Reset() (bool, error) OnReset() error IsRunning() bool String() string } type BaseService struct { log log15.Logger name string started uint32 // atomic stopped uint32 // atomic // The "subclass" of BaseService impl Service } func NewBaseService(log log15.Logger, name string, impl Service) *BaseService { return &BaseService{ log: log, name: name, impl: impl, } } // Implements Servce func (bs *BaseService) Start() (bool, error) { if atomic.CompareAndSwapUint32(&bs.started, 0, 1) { if atomic.LoadUint32(&bs.stopped) == 1 { if bs.log != nil { bs.log.Warn(Fmt("Not starting %v -- already stopped", bs.name), "impl", bs.impl) } return false, nil } else { if bs.log != nil { bs.log.Info(Fmt("Starting %v", bs.name), "impl", bs.impl) } } err := bs.impl.OnStart() return true, err } else { if bs.log != nil { bs.log.Debug(Fmt("Not starting %v -- already started", bs.name), "impl", bs.impl) } return false, nil } } // Implements Service func (bs *BaseService) OnStart() error { return nil } // Implements Service func (bs *BaseService) Stop() bool { if atomic.CompareAndSwapUint32(&bs.stopped, 0, 1) { if bs.log != nil { bs.log.Info(Fmt("Stopping %v", bs.name), "impl", bs.impl) } bs.impl.OnStop() return true } else { if bs.log != nil { bs.log.Debug(Fmt("Stopping %v (ignoring: already stopped)", bs.name), "impl", bs.impl) } return false } } // Implements Service func (bs *BaseService) OnStop() {} // Implements Service func (bs *BaseService) Reset() (bool, error) { if atomic.CompareAndSwapUint32(&bs.stopped, 1, 0) { // whether or not we've started, we can reset atomic.CompareAndSwapUint32(&bs.started, 1, 0) return true, bs.impl.OnReset() } else { if bs.log != nil { bs.log.Debug(Fmt("Can't reset %v. Not stopped", bs.name), "impl", bs.impl) } return false, nil } // never happens return false, nil } // Implements Service func (bs *BaseService) OnReset() error { PanicSanity("The service cannot be reset") return nil } // Implements Service func (bs *BaseService) IsRunning() bool { return atomic.LoadUint32(&bs.started) == 1 && atomic.LoadUint32(&bs.stopped) == 0 } // Implements Servce func (bs *BaseService) String() string { return bs.name } //---------------------------------------- type QuitService struct { BaseService Quit chan struct{} } func NewQuitService(log log15.Logger, name string, impl Service) *QuitService { return &QuitService{ BaseService: *NewBaseService(log, name, impl), Quit: nil, } } // NOTE: when overriding OnStart, must call .QuitService.OnStart(). func (qs *QuitService) OnStart() error { qs.Quit = make(chan struct{}) return nil } // NOTE: when overriding OnStop, must call .QuitService.OnStop(). func (qs *QuitService) OnStop() { if qs.Quit != nil { close(qs.Quit) } }