Documentation ¶
Index ¶
- func StartAndAwaitRunning(ctx context.Context, service Service) error
- func StartManagerAndAwaitHealthy(ctx context.Context, manager *Manager) error
- func StopAndAwaitTerminated(ctx context.Context, service Service) error
- func StopManagerAndAwaitStopped(ctx context.Context, manager *Manager) error
- type BasicService
- func (b *BasicService) AddListener(listener Listener)
- func (b *BasicService) AwaitRunning(ctx context.Context) error
- func (b *BasicService) AwaitTerminated(ctx context.Context) error
- func (b *BasicService) FailureCase() error
- func (b *BasicService) ServiceContext() context.Context
- func (b *BasicService) StartAsync(parentContext context.Context) error
- func (b *BasicService) State() State
- func (b *BasicService) StopAsync()
- type FailureWatcher
- type Listener
- type Manager
- func (m *Manager) AddListener(listener ManagerListener)
- func (m *Manager) AwaitHealthy(ctx context.Context) error
- func (m *Manager) AwaitStopped(ctx context.Context) error
- func (m *Manager) IsHealthy() bool
- func (m *Manager) IsStopped() bool
- func (m *Manager) ServicesByState() map[State][]Service
- func (m *Manager) StartAsync(ctx context.Context) error
- func (m *Manager) StopAsync()
- type ManagerListener
- type OneIteration
- type RunningFn
- type Service
- type StartingFn
- type State
- type StoppingFn
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func StartAndAwaitRunning ¶
StartAndAwaitRunning starts the service, and then waits until it reaches Running state. If service fails to start, its failure case is returned. Service must be in New state when this function is called.
Notice that context passed to the service for starting is the same as context used for waiting! If you need these contexts to be different, please use StartAsync and AwaitRunning directly.
func StartManagerAndAwaitHealthy ¶
StartManagerAndAwaitHealthy starts the manager (which in turns starts all services managed by it), and then waits until it reaches Running state. All services that this manager manages must be in New state, otherwise starting will fail.
Notice that context passed to the manager for starting its services is the same as context used for waiting!
func StopAndAwaitTerminated ¶
StopAndAwaitTerminated asks service to stop, and then waits until service reaches Terminated or Failed state. If service ends in Terminated state, this function returns error. On Failed state, it returns the failure case. Other errors are possible too (eg. if context stops before service does).
Types ¶
type BasicService ¶
type BasicService struct {
// contains filtered or unexported fields
}
BasicService implements contract of Service interface, using three supplied functions: StartingFn, RunningFn and StoppingFn. When service is started, these three functions are called as service transitions to Starting, Running and Stopping state.
Since they are called sequentially, they don't need to synchronize access on the state. (In other words: StartingFn happens-before RunningFn, RunningFn happens-before StoppingFn).
All three functions are called at most once. If they are nil, they are not called and service transitions to the next state.
Context passed to StartingFn and RunningFn function is canceled when StopAsync() is called, or service enters Stopping state. This context can be used to start additional tasks from inside StartingFn or RunningFn. Same context is available via ServiceContext() method (not part of Service interface).
Possible orders of how functions are called:
* 1. StartingFn -- if StartingFn returns error, no other functions are called.
* 1. StartingFn, 2. StoppingFn -- StartingFn doesn't return error, but StopAsync is called while running StartingFn, or context is canceled from outside while StartingFn still runs.
* 1. StartingFn, 2. RunningFn, 3. StoppingFn -- this is most common, when StartingFn doesn't return error, service is not stopped and context isn't stopped externally while running StartingFn.
func NewBasicService ¶
func NewBasicService(start StartingFn, run RunningFn, stop StoppingFn) *BasicService
Returns service built from three functions (using BasicService).
func (*BasicService) AddListener ¶
func (b *BasicService) AddListener(listener Listener)
AddListener is part of Service interface.
func (*BasicService) AwaitRunning ¶
func (b *BasicService) AwaitRunning(ctx context.Context) error
AwaitRunning is part of Service interface.
func (*BasicService) AwaitTerminated ¶
func (b *BasicService) AwaitTerminated(ctx context.Context) error
AwaitTerminated is part of Service interface.
func (*BasicService) FailureCase ¶
func (b *BasicService) FailureCase() error
FailureCase is part of Service interface.
func (*BasicService) ServiceContext ¶
func (b *BasicService) ServiceContext() context.Context
Returns context that this service uses internally for controlling its lifecycle. It is the same context that is passed to Starting and Running functions, and is based on context passed to the service via StartAsync.
Before service enters Starting state, there is no context. This context is stopped when service enters Stopping state.
This can be useful in code, that embeds BasicService and wants to provide additional methods to its clients.
Example:
func (s *exampleService) Send(msg string) bool { ctx := s.ServiceContext() if ctx == nil { // Service is not yet started return false } select { case s.ch <- msg: return true case <-ctx.Done(): // Service is not running anymore. return false } }
This is not part of Service interface, and clients of the Service should not use it.
func (*BasicService) StartAsync ¶
func (b *BasicService) StartAsync(parentContext context.Context) error
StartAsync is part of Service interface.
func (*BasicService) State ¶
func (b *BasicService) State() State
State is part of Service interface.
func (*BasicService) StopAsync ¶
func (b *BasicService) StopAsync()
StopAsync is part of Service interface.
type FailureWatcher ¶ added in v1.0.0
type FailureWatcher struct {
// contains filtered or unexported fields
}
FailureWatcher waits for service failures, and passed them to the channel.
func NewFailureWatcher ¶ added in v1.0.0
func NewFailureWatcher() *FailureWatcher
func (*FailureWatcher) Chan ¶ added in v1.0.0
func (w *FailureWatcher) Chan() <-chan error
Returns channel for this watcher. If watcher is nil, returns nil channel. Errors returned on the channel include failure case and service description.
func (*FailureWatcher) WatchManager ¶ added in v1.0.0
func (w *FailureWatcher) WatchManager(manager *Manager)
func (*FailureWatcher) WatchService ¶ added in v1.0.0
func (w *FailureWatcher) WatchService(service Service)
type Listener ¶
type Listener interface { // Called when the service transitions from NEW to STARTING. Starting() // Called when the service transitions from STARTING to RUNNING. Running() // Called when the service transitions to the STOPPING state. Stopping(from State) // Called when the service transitions to the TERMINATED state. Terminated(from State) // Called when the service transitions to the FAILED state. Failed(from State, failure error) }
Listener receives notifications about Service state changes.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Service Manager is initialized with a collection of services. They all must be in New state. Service manager can start them, and observe their state as a group. Once all services are running, Manager is said to be Healthy. It is possible for manager to never reach the Healthy state, if some services fail to start. When all services are stopped (Terminated or Failed), manager is Stopped.
func NewManager ¶
NewManager creates new service manager. It needs at least one service, and all services must be in New state.
func (*Manager) AddListener ¶
func (m *Manager) AddListener(listener ManagerListener)
Registers a ManagerListener to be run when this Manager changes state. The listener will not have previous state changes replayed, so it is suggested that listeners are added before any of the managed services are started.
AddListener guarantees execution ordering across calls to a given listener but not across calls to multiple listeners. Specifically, a given listener will have its callbacks invoked in the same order as the underlying service enters those states. Additionally, at most one of the listener's callbacks will execute at once. However, multiple listeners' callbacks may execute concurrently, and listeners may execute in an order different from the one in which they were registered.
func (*Manager) AwaitHealthy ¶
Waits for the ServiceManager to become healthy. Returns nil, if manager is healthy, error otherwise (eg. manager is in a state in which it cannot get healthy anymore).
func (*Manager) AwaitStopped ¶
Waits for the ServiceManager to become stopped. Returns nil, if manager is stopped, error when context finishes earlier.
func (*Manager) IsStopped ¶
Returns true if all services are in terminal state (Terminated or Failed)
func (*Manager) ServicesByState ¶
Provides a snapshot of the current state of all the services under management.
func (*Manager) StartAsync ¶
Initiates service startup on all the services being managed. It is only valid to call this method if all of the services are New.
type ManagerListener ¶
type ManagerListener interface { // Called when Manager reaches Healthy state (all services Running) Healthy() // Called when Manager reaches Stopped state (all services are either Terminated or Failed) Stopped() // Called when service fails. Failure(service Service) }
ManagerListener listens for events from Manager.
func NewManagerListener ¶
func NewManagerListener(healthy, stopped func(), failure func(service Service)) ManagerListener
NewManagerListener provides a simple way to build manager listener from supplied functions. Functions will only be called when not nil.
type OneIteration ¶
One iteration of the timer service. Called repeatedly until service is stopped, or this function returns error in which case, service will fail.
type RunningFn ¶
RunningFn function is called when service enters Running state. When it returns, service will move to Stopping state. If RunningFn or Stopping return error, Service will end in Failed state, otherwise if both functions return without error, service will end in Terminated state.
type Service ¶
type Service interface { // Starts Service asynchronously. Service must be in New State, otherwise error is returned. // Context is used as a parent context for service own context. StartAsync(ctx context.Context) error // Waits until service gets into Running state. // If service is in New or Starting state, this method is blocking. // If service is already in Running state, returns immediately with no error. // If service is in a state, from which it cannot get into Running state, error is returned immediately. AwaitRunning(ctx context.Context) error // Tell the service to stop. This method doesn't block and can be called multiple times. // If Service is New, it is Terminated without having been started nor stopped. // If Service is in Starting or Running state, this initiates shutdown and returns immediately. // If Service has already been stopped, this method returns immediately, without taking action. StopAsync() // Waits for the service to reach Terminated or Failed state. If service is already in one of these states, // when method is called, method returns immediately. // If service enters Terminated state, this method returns nil. // If service enters Failed state, or context is finished before reaching Terminated or Failed, error is returned. AwaitTerminated(ctx context.Context) error // If Service is in Failed state, this method returns the error. // If Service is not in Failed state, this method returns nil. FailureCase() error // Returns current state of the service. State() State // AddListener adds listener to this service. Listener will be notified on subsequent state transitions // of the service. Previous state transitions are not replayed, so it is suggested to add listeners before // service is started. // // AddListener guarantees execution ordering across calls to a given listener but not across calls to // multiple listeners. Specifically, a given listener will have its callbacks invoked in the same order // as the service enters those states. Additionally, at most one of the listener's callbacks will execute // at once. However, multiple listeners' callbacks may execute concurrently, and listeners may execute // in an order different from the one in which they were registered. AddListener(listener Listener) }
Service defines interface for controlling a service.
State diagram for the service:
┌────────────────────────────────────────────────────────────────────┐ │ │ │ ▼ ┌─────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐ │ New │─────▶│ Starting │─────▶│ Running │────▶│ Stopping │───┬─▶│ Terminated │ └─────┘ └──────────┘ └─────────┘ └──────────┘ │ └────────────┘ │ │ │ │ │ │ ┌────────┐ └──────────────────────────────────────────┴──▶│ Failed │ └────────┘
Example ¶
package main import ( "context" "fmt" ) type exampleService struct { *BasicService log []string ch chan string } func newExampleServ() *exampleService { s := &exampleService{ ch: make(chan string), } s.BasicService = NewBasicService(nil, s.collect, nil) // StartingFn, RunningFn, StoppingFn return s } // used as Running function. When service is stopped, context is canceled, so we react on it. func (s *exampleService) collect(ctx context.Context) error { for { select { case <-ctx.Done(): return nil case msg := <-s.ch: s.log = append(s.log, msg) } } } // External method called by clients of the Service. func (s *exampleService) Send(msg string) bool { ctx := s.ServiceContext() if ctx == nil { // Service is not yet started return false } select { case s.ch <- msg: return true case <-ctx.Done(): // Service is not running anymore. return false } } func main() { es := newExampleServ() es.Send("first") // ignored, as service is not running yet _ = es.StartAsync(context.Background()) _ = es.AwaitRunning(context.Background()) es.Send("second") es.StopAsync() _ = es.AwaitTerminated(context.Background()) es.Send("third") // ignored, service is now stopped fmt.Println(es.log) }
Output: [second]
func NewIdleService ¶
func NewIdleService(up StartingFn, down StoppingFn) Service
Initializes basic service as an "idle" service -- it doesn't do anything in its Running state, but still supports all state transitions.
func NewTimerService ¶
func NewTimerService(interval time.Duration, start StartingFn, iter OneIteration, stop StoppingFn) Service
Runs iteration function on every interval tick. When iteration returns error, service fails.
type StartingFn ¶
StartingFn is called when service enters Starting state. If StartingFn returns error, service goes into Failed state. If StartingFn returns without error, service transitions into Running state (unless context has been canceled).
serviceContext is a context that is finished at latest when service enters Stopping state, but can also be finished earlier when StopAsync is called on the service. This context is derived from context passed to StartAsync method.
type State ¶
type State int
State of the service. See Service interface for full state diagram.
const ( New State = iota // Service is new, not running yet. Initial state. Starting // Service is starting. If starting succeeds, service enters Running state. Running // Service is fully running now. When service stops running, it enters Stopping state. Stopping // Service is shutting down Terminated // Service has stopped successfully. Terminal state. Failed // Service has failed in Starting, Running or Stopping state. Terminal state. )
type StoppingFn ¶
StoppingFn function is called when service enters Stopping state. When it returns, service moves to Terminated or Failed state, depending on whether there was any error returned from previous RunningFn (if it was called) and this StoppingFn function. If both return error, RunningFn's error will be saved as failure case for Failed state. Parameter is error from Running function, or nil if there was no error.