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
nil
error
- 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
Killable
is not dead until all managed goroutines have returned. - If the goroutine returns a non
nil
error
, theKillable
starts dying. - If the
Killable
is already dying when theGo
method 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
Killable
s 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
reason
isErrKillLocal
, 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
}