From b0beb12fcfbbf9a778f13c6e4e6cb3d4033c5a0c Mon Sep 17 00:00:00 2001 From: Mark Beamer Jr Date: Fri, 28 Dec 2018 12:15:03 -0500 Subject: [PATCH] Added package documentation and removed sync creation in New since it is not needed unless the stopper is initialized with `NewDebug` --- stop/stop.go | 79 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/stop/stop.go b/stop/stop.go index 935d491..86dc9db 100644 --- a/stop/stop.go +++ b/stop/stop.go @@ -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 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. func New(parent ...*Group) *Group { - s := &Group{mu: &sync.Mutex{}} + s := &Group{} ctx := context.Background() if len(parent) > 0 && parent[0] != nil { ctx = parent[0].ctx @@ -31,6 +45,15 @@ func New(parent ...*Group) *Group { 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. func (s *Group) Ch() Chan { return s.ctx.Done() @@ -52,44 +75,50 @@ func (s *Group) Child() *Group { 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.mu.Lock() - defer s.mu.Unlock() + if s.waitingOn != nil { + s.mu.Lock() + defer s.mu.Unlock() - if s.waitingOn == nil { - s.waitingOn = make(map[string]int) - } + if s.waitingOn == nil { + s.waitingOn = make(map[string]int) + } - if current, ok := s.waitingOn[name]; ok { - s.waitingOn[name] = current + 1 - } else { - s.waitingOn[name] = 1 + if current, ok := s.waitingOn[name]; ok { + s.waitingOn[name] = current + 1 + } else { + 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() - s.mu.Lock() - defer s.mu.Unlock() + if s.waitingOn != nil { + s.mu.Lock() + defer s.mu.Unlock() - if current, ok := s.waitingOn[name]; ok { - if current <= 1 { - delete(s.waitingOn, name) + if current, ok := s.waitingOn[name]; ok { + if current <= 1 { + delete(s.waitingOn, name) + } else { + s.waitingOn[name] = current - 1 + } } else { - s.waitingOn[name] = current - 1 + log.Printf("%s is not recorded in stop group map", name) } - } else { - 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 { - if v > 0 { - log.Printf("waiting on %d %s routines...", v, k) + for k, v := range s.waitingOn { + if v > 0 { + log.Printf("waiting on %d %s routines...", v, k) + } } } }