Merge pull request #49 from lbryio/stopper-debug
improve stopper debugging
This commit is contained in:
commit
1c7c0acc88
1 changed files with 49 additions and 21 deletions
70
stop/stop.go
70
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 (
|
||||
|
@ -11,11 +25,12 @@ type Chan <-chan struct{}
|
|||
|
||||
// Stopper extends sync.WaitGroup to add a convenient way to stop running goroutines
|
||||
type Group struct {
|
||||
waitingOn map[string]int
|
||||
l sync.RWMutex
|
||||
sync.WaitGroup
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
mu *sync.Mutex
|
||||
waitingOn map[string]int
|
||||
}
|
||||
type Stopper = Group
|
||||
|
||||
|
@ -34,7 +49,7 @@ func New(parent ...*Group) *Group {
|
|||
func NewDebug(parent ...*Group) *Group {
|
||||
s := New(parent...)
|
||||
s.waitingOn = make(map[string]int)
|
||||
s.l = sync.RWMutex{}
|
||||
s.mu = &sync.Mutex{}
|
||||
|
||||
return s
|
||||
}
|
||||
|
@ -60,37 +75,50 @@ func (s *Group) Child() *Group {
|
|||
return New(s)
|
||||
}
|
||||
|
||||
//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) {
|
||||
if s.waitingOn != nil {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
_, ok := s.waitingOn[name]
|
||||
if !ok {
|
||||
s.waitingOn[name] = 1
|
||||
} else {
|
||||
s.waitingOn[name] = s.waitingOn[name] + 1
|
||||
}
|
||||
}
|
||||
s.Add(delta)
|
||||
|
||||
if s.waitingOn != nil {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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()
|
||||
|
||||
if s.waitingOn != nil {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
_, ok := s.waitingOn[name]
|
||||
if !ok {
|
||||
log.Printf("%s is not recorded in stop group map")
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if current, ok := s.waitingOn[name]; ok {
|
||||
if current <= 1 {
|
||||
delete(s.waitingOn, name)
|
||||
} else {
|
||||
s.waitingOn[name] = current - 1
|
||||
}
|
||||
} else {
|
||||
s.waitingOn[name] = s.waitingOn[name] - 1
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Done()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue