stop

package
v2.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 16, 2021 License: MIT Imports: 3 Imported by: 21

README

Stop Group

A stop group is meant to help cleanly shut down a component that uses multiple goroutines.

The Problem

A shutdown typically works as follows:

  • a component receives a shutdown signal
  • the component passes the shutdown signal to each outstanding goroutine
  • each goroutine stops whatever it is doing and returns
  • once all the goroutines have returned, the component does any final cleanup and signals that it is done

There are several gotchas in this process:

  • each goroutine must be tracked, so all can be stopped
  • the component should not expose implementation details about how many goroutines it uses and how they are managed
  • goroutines may be blocked by a read on a channel. they need to be unblocked during shutdown
  • goroutines may take a while to finish, and may finish in any order. component shutdown is not complete until all goroutines finish
  • using a channel to send the shutdown signal is complicated (doing things in the correct order, closing an already-closed channel, etc)

The Solution

The solution is a stop group. A stop group is a combination of a sync.WaitGroup and a cancelable context. Here's how it works:

grp := stop.New()
action := func() { ... }

All goroutines are started in the start group.

grp.Add(1)
go func() {
  defer grp.Done()
  action()
}

Any goroutine that may be blocked by a channel read has a simple way of unblocking on shutdown

for {
  select {
  case text := <-actionCh:
    fmt.Printf("Got some text: %s", text)
  case <-grp.Ch():
    return
  }
}

Shutting down synchronously is easy

grp.StopAndWait()

Example

Structure
type Server struct {
	grp   *stop.Group
}

func NewServer() *Server {
	return &Server{
		grp:     stop.New(),
	}
}

func (s *Server) Shutdown() {
	s.grp.StopAndWait()
}

func (s *Server) Start(address string) error {
	l, err := net.Listen(network, address)
	if err != nil {
		return err
	}
	log.Println("listening on " + address)

	s.grp.Add(1)
	go func() {
		defer s.grp.Done()
		for {
			select {
			case <-s.grp.Ch():
				return
			case <-time.Tick(10 * time.Second):
				log.Println("still running")
			}
		}
	}()

	s.grp.Add(1)
	go func() {
		defer s.grp.Done()
		<-s.grp.Ch()
		err := l.Close()
		if err != nil {
			log.Errorln(err)
		}
	}()

	// listenAndServe blocks until the server is shut down, just like http.ListenAndServe()
	return s.listenAndServe(l)
}
Usage
s := NewServer()
log.Println("starting")
go s.Start("localhost:1234")

// ... do some other things here ...

log.Println("shutting down")
s.Shutdown()
log.Println("shutdown complete")

Documentation

Overview

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.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Chan

type Chan <-chan struct{}

Chan is a receive-only channel

type Group

type Group struct {
	sync.WaitGroup
	// contains filtered or unexported fields
}

Stopper extends sync.WaitGroup to add a convenient way to stop running goroutines

func New

func New(parent ...*Group) *Group

New allocates and returns a new instance. Use New(parent) to create an instance that is stopped when parent is stopped.

func NewDebug

func NewDebug(parent ...*Group) *Group

NewDebug allows you to debug the go routines the group waits on. In order to leverage this, AddNamed and DoneNamed should be used.

func (*Group) AddNamed

func (s *Group) AddNamed(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 (*Group) Ch

func (s *Group) Ch() Chan

Ch returns a channel that will be closed when Stop is called.

func (*Group) Child

func (s *Group) Child() *Group

Child returns a new instance that will be stopped when s is stopped.

func (*Group) DoneNamed

func (s *Group) DoneNamed(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 (*Group) Stop

func (s *Group) Stop()

Stop signals any listening processes to stop. After the first call, Stop() does nothing.

func (*Group) StopAndWait

func (s *Group) StopAndWait()

StopAndWait is a convenience method to close the channel and wait for goroutines to return.

type Stopper

type Stopper = Group

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL