killable
killable copied to clipboard
Composable cancellation
DEPRECATED - THIS PACKAGE IS BROKEN AND I'M NOT GOING TO FIX IT
Killable 
A package for graceful shutdowns (inspired by tomb)
States
A Killable represents a group of goroutines. It goes through 3 stages:
- Alive - The goroutines are running
- Dying - The goroutines are being signaled to terminate
- Dead - All managed goroutines have terminated

There are two ways a Killable can enter the dying state.
- One of the managed goroutines returns a non
nilerror - The
Kill(error)method is invoked on theKillable.
Managed Goroutines
Goroutines managed by the Killable are started with killable.Go
k := killable.New()
go func() {
<-k.Dying()
fmt.Println("Dying")
<-k.Dead()
fmt.Println("Dead")
}()
killable.Go(k, func() error {
time.Sleep(5 * time.Second)
fmt.Println("Finished sleeping, i'll be dead soon")
return nil
})
k.Kill(fmt.Errorf("it's time to die!"))
- A
Killableis not dead until all managed goroutines have returned. - If the goroutine returns a non
nilerror, theKillablestarts dying. - If the
Killableis already dying when theGomethod is invoked, it does not run.
Defer
Defer is similar to the defer keyword.
func Connect(k killable.Killable) (*sql.DB, error) {
db, err := sql.Open("foo", "bar")
if err != nil {
return nil, err
}
// clean up resources near instantiation
killable.Defer(k, func() {
db.Close()
})
return db, nil
}
- Deferred methods are called once the killable is dead.
- Deferred methods are invoked in the opposite order they were defined (lifo).
Linking
Killables can be linked to eachother in a parent/child relationship.
- If a child is killed, the parent is also killed.
- If the parent is killed, it kills all the children.
- If the
reasonisErrKillLocal, the parent ignores it. - The parent doesn't die until all the children are dead
func makeChild(d time.Duration) killable.Killable {
k := killable.New()
killable.Go(k, func() {
// Sleep will immediately return ErrDying if the Killable
// enters the dying state during the sleep
// (see source to see how to implement similar methods)
if err := killable.Sleep(k, d); err != nil {
return err
}
return killable.ErrKill
})
return k
}
var (
// children
k1 = makeChild(4 * time.Second)
k2 = makeChild(3 * time.Second)
k3 = makeChild(2 * time.Second)
// parent
k4 = killable.New(k1, k2, k3)
)
killable.Defer(k4, func() {
fmt.Println("All children are dead!")
})
go func() {
<-k4.Dying()
fmt.Println("Killing all children")
}()

See examples/ directory.
The methods like Defer, Go, Do, etc ... have been placed in the packages because the Killable type is meant to be embedded. The interface the Killable type exposes makes sense without understanding the killable package.
Context
Since Go 1.7, the many standard library functions have support for context.Context. The killable.Killable interface cannot be changed to conform to the context.Context interface because the semantics of the Err method. To get around this, killable.Killable has a .Context().
func DoQuery(k killable.Killable, db *sql.DB) error {
_, err := db.ExecContext(k.Context(), "INSERT foo INTO bar")
return err
}