Added package documentation and removed sync creation in New since it is not needed unless the stopper is initialized with NewDebug

This commit is contained in:
Mark Beamer Jr 2018-12-28 12:15:03 -05:00
parent 2268aecd68
commit b0beb12fcf
No known key found for this signature in database
GPG key ID: 1C314FB89AD76973

View file

@ -1,3 +1,17 @@
/*Package stop implements the stopper pattern for golang concurrency management. The main use case is to gracefully
exit an application. The pattern allows for a hierarchy of stoppers. Each package should have its own unexported stopper.
The package should maintain startup and shutdown exported methods. If the stopper should stop when another stopper for
a different package stops, the parent argument for the initialization of a stopper be used to create the dependency.
The package also comes with a debugging tool to help in determining why and where a stopper is not stopping as expected.
If a more complex concurrency is used, it is recommended to implement the library using `DoneNamed` and `AddNamed`.
In addition to the standard `Done` functionality, it allows a functional named representation of the type of go routine
being completed. This works in conjunction with `AddNamed`. If the init of the stopper Group happens with `NewDebug`
instead of `New` special tracking and logging is enabled where it will print out the remaining routines' functional name
and how many it is waiting on to complete the stop after each call to `DoneNamed`. This allows easy debugging by just
changing the init of the stopper instead of all the references as long as the library is implemented with `DoneNamed`
and `AddNamed`. For simple uses of the stopper pattern this is not needed and the standard `Add` and `Done` should be used.
*/
package stop package stop
import ( import (
@ -22,7 +36,7 @@ type Stopper = Group
// New allocates and returns a new instance. Use New(parent) to create an instance that is stopped when parent is stopped. // New allocates and returns a new instance. Use New(parent) to create an instance that is stopped when parent is stopped.
func New(parent ...*Group) *Group { func New(parent ...*Group) *Group {
s := &Group{mu: &sync.Mutex{}} s := &Group{}
ctx := context.Background() ctx := context.Background()
if len(parent) > 0 && parent[0] != nil { if len(parent) > 0 && parent[0] != nil {
ctx = parent[0].ctx ctx = parent[0].ctx
@ -31,6 +45,15 @@ func New(parent ...*Group) *Group {
return s return s
} }
// NewDebug allows you to debug the go routines the group waits on. In order to leverage this, AddNamed and DoneNamed should be used.
func NewDebug(parent ...*Group) *Group {
s := New(parent...)
s.waitingOn = make(map[string]int)
s.mu = &sync.Mutex{}
return s
}
// Ch returns a channel that will be closed when Stop is called. // Ch returns a channel that will be closed when Stop is called.
func (s *Group) Ch() Chan { func (s *Group) Ch() Chan {
return s.ctx.Done() return s.ctx.Done()
@ -52,9 +75,11 @@ func (s *Group) Child() *Group {
return New(s) return New(s)
} }
func (s *Group) DebugAdd(delta int, name string) { //AddNamed is the same as Add but will register the functional name of the routine for later output. See `DoneNamed`.
func (s *Group) AddNamed(delta int, name string) {
s.Add(delta) s.Add(delta)
if s.waitingOn != nil {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -67,11 +92,14 @@ func (s *Group) DebugAdd(delta int, name string) {
} else { } else {
s.waitingOn[name] = 1 s.waitingOn[name] = 1
} }
}
} }
func (s *Group) DebugDone(name string) { //DoneNamed is the same as `Done` but will output the functional name of all remaining named routines and the waiting on count.
func (s *Group) DoneNamed(name string) {
defer s.Done() defer s.Done()
if s.waitingOn != nil {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -85,11 +113,12 @@ func (s *Group) DebugDone(name string) {
log.Printf("%s is not recorded in stop group map", name) log.Printf("%s is not recorded in stop group map", name)
} }
log.Printf("-->> LIST WAITING ON") log.Printf("-->> LIST ROUTINES WAITING ON")
for k, v := range s.waitingOn { for k, v := range s.waitingOn {
if v > 0 { if v > 0 {
log.Printf("waiting on %d %s routines...", v, k) log.Printf("waiting on %d %s routines...", v, k)
} }
} }
}
} }