e3e545e22e
This also adds a package docs for the stopper package.
98 lines
2.3 KiB
Go
98 lines
2.3 KiB
Go
// Package stopper implements a pattern for shutting down a group of processes.
|
|
package stopper
|
|
|
|
import (
|
|
"sync"
|
|
)
|
|
|
|
// AlreadyStopped is a closed error channel to be used by Funcs when
|
|
// an element was already stopped.
|
|
var AlreadyStopped <-chan error
|
|
|
|
// AlreadyStoppedFunc is a Func that returns AlreadyStopped.
|
|
var AlreadyStoppedFunc = func() <-chan error { return AlreadyStopped }
|
|
|
|
func init() {
|
|
closeMe := make(chan error)
|
|
close(closeMe)
|
|
AlreadyStopped = closeMe
|
|
}
|
|
|
|
// Stopper is an interface that allows a clean shutdown.
|
|
type Stopper interface {
|
|
// Stop returns a channel that indicates whether the stop was
|
|
// successful.
|
|
// The channel can either return one error or be closed. Closing the
|
|
// channel signals a clean shutdown.
|
|
// The Stop function should return immediately and perform the actual
|
|
// shutdown in a separate goroutine.
|
|
Stop() <-chan error
|
|
}
|
|
|
|
// StopGroup is a group that can be stopped.
|
|
type StopGroup struct {
|
|
stoppables []Func
|
|
sync.Mutex
|
|
}
|
|
|
|
// Func is a function that can be used to provide a clean shutdown.
|
|
type Func func() <-chan error
|
|
|
|
// NewStopGroup creates a new StopGroup.
|
|
func NewStopGroup() *StopGroup {
|
|
return &StopGroup{
|
|
stoppables: make([]Func, 0),
|
|
}
|
|
}
|
|
|
|
// Add adds a Stopper to the StopGroup.
|
|
// On the next call to Stop(), the Stopper will be stopped.
|
|
func (cg *StopGroup) Add(toAdd Stopper) {
|
|
cg.Lock()
|
|
defer cg.Unlock()
|
|
|
|
cg.stoppables = append(cg.stoppables, toAdd.Stop)
|
|
}
|
|
|
|
// AddFunc adds a Func to the StopGroup.
|
|
// On the next call to Stop(), the Func will be called.
|
|
func (cg *StopGroup) AddFunc(toAddFunc Func) {
|
|
cg.Lock()
|
|
defer cg.Unlock()
|
|
|
|
cg.stoppables = append(cg.stoppables, toAddFunc)
|
|
}
|
|
|
|
// Stop stops all members of the StopGroup.
|
|
// Stopping will be done in a concurrent fashion.
|
|
// The slice of errors returned contains all errors returned by stopping the
|
|
// members.
|
|
func (cg *StopGroup) Stop() []error {
|
|
cg.Lock()
|
|
defer cg.Unlock()
|
|
|
|
var errors []error
|
|
whenDone := make(chan struct{})
|
|
|
|
waitChannels := make([]<-chan error, 0, len(cg.stoppables))
|
|
for _, toStop := range cg.stoppables {
|
|
waitFor := toStop()
|
|
if waitFor == nil {
|
|
panic("received a nil chan from Stop")
|
|
}
|
|
waitChannels = append(waitChannels, waitFor)
|
|
}
|
|
|
|
go func() {
|
|
for _, waitForMe := range waitChannels {
|
|
err := <-waitForMe
|
|
if err != nil {
|
|
errors = append(errors, err)
|
|
}
|
|
}
|
|
close(whenDone)
|
|
}()
|
|
|
|
<-whenDone
|
|
return errors
|
|
}
|