224 lines
6.8 KiB
Go
224 lines
6.8 KiB
Go
// Copyright (c) 2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of the copyright holder nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
// The tomb package handles clean goroutine tracking and termination.
|
|
//
|
|
// The zero value of a Tomb is ready to handle the creation of a tracked
|
|
// goroutine via its Go method, and then any tracked goroutine may call
|
|
// the Go method again to create additional tracked goroutines at
|
|
// any point.
|
|
//
|
|
// If any of the tracked goroutines returns a non-nil error, or the
|
|
// Kill or Killf method is called by any goroutine in the system (tracked
|
|
// or not), the tomb Err is set, Alive is set to false, and the Dying
|
|
// channel is closed to flag that all tracked goroutines are supposed
|
|
// to willingly terminate as soon as possible.
|
|
//
|
|
// Once all tracked goroutines terminate, the Dead channel is closed,
|
|
// and Wait unblocks and returns the first non-nil error presented
|
|
// to the tomb via a result or an explicit Kill or Killf method call,
|
|
// or nil if there were no errors.
|
|
//
|
|
// It is okay to create further goroutines via the Go method while
|
|
// the tomb is in a dying state. The final dead state is only reached
|
|
// once all tracked goroutines terminate, at which point calling
|
|
// the Go method again will cause a runtime panic.
|
|
//
|
|
// Tracked functions and methods that are still running while the tomb
|
|
// is in dying state may choose to return ErrDying as their error value.
|
|
// This preserves the well established non-nil error convention, but is
|
|
// understood by the tomb as a clean termination. The Err and Wait
|
|
// methods will still return nil if all observed errors were either
|
|
// nil or ErrDying.
|
|
//
|
|
// For background and a detailed example, see the following blog post:
|
|
//
|
|
// http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
|
|
//
|
|
package tomb
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// A Tomb tracks the lifecycle of one or more goroutines as alive,
|
|
// dying or dead, and the reason for their death.
|
|
//
|
|
// See the package documentation for details.
|
|
type Tomb struct {
|
|
m sync.Mutex
|
|
alive int
|
|
dying chan struct{}
|
|
dead chan struct{}
|
|
reason error
|
|
}
|
|
|
|
var (
|
|
ErrStillAlive = errors.New("tomb: still alive")
|
|
ErrDying = errors.New("tomb: dying")
|
|
)
|
|
|
|
func (t *Tomb) init() {
|
|
t.m.Lock()
|
|
if t.dead == nil {
|
|
t.dead = make(chan struct{})
|
|
t.dying = make(chan struct{})
|
|
t.reason = ErrStillAlive
|
|
}
|
|
t.m.Unlock()
|
|
}
|
|
|
|
// Dead returns the channel that can be used to wait until
|
|
// all goroutines have finished running.
|
|
func (t *Tomb) Dead() <-chan struct{} {
|
|
t.init()
|
|
return t.dead
|
|
}
|
|
|
|
// Dying returns the channel that can be used to wait until
|
|
// t.Kill is called.
|
|
func (t *Tomb) Dying() <-chan struct{} {
|
|
t.init()
|
|
return t.dying
|
|
}
|
|
|
|
// Wait blocks until all goroutines have finished running, and
|
|
// then returns the reason for their death.
|
|
func (t *Tomb) Wait() error {
|
|
t.init()
|
|
<-t.dead
|
|
t.m.Lock()
|
|
reason := t.reason
|
|
t.m.Unlock()
|
|
return reason
|
|
}
|
|
|
|
// Go runs f in a new goroutine and tracks its termination.
|
|
//
|
|
// If f returns a non-nil error, t.Kill is called with that
|
|
// error as the death reason parameter.
|
|
//
|
|
// It is f's responsibility to monitor the tomb and return
|
|
// appropriately once it is in a dying state.
|
|
//
|
|
// It is safe for the f function to call the Go method again
|
|
// to create additional tracked goroutines. Once all tracked
|
|
// goroutines return, the Dead channel is closed and the
|
|
// Wait method unblocks and returns the death reason.
|
|
//
|
|
// Calling the Go method after all tracked goroutines return
|
|
// causes a runtime panic. For that reason, calling the Go
|
|
// method a second time out of a tracked goroutine is unsafe.
|
|
func (t *Tomb) Go(f func() error) {
|
|
t.init()
|
|
t.m.Lock()
|
|
defer t.m.Unlock()
|
|
select {
|
|
case <-t.dead:
|
|
panic("tomb.Go called after all goroutines terminated")
|
|
default:
|
|
}
|
|
t.alive++
|
|
go t.run(f)
|
|
}
|
|
|
|
func (t *Tomb) run(f func() error) {
|
|
err := f()
|
|
t.m.Lock()
|
|
defer t.m.Unlock()
|
|
t.alive--
|
|
if t.alive == 0 || err != nil {
|
|
t.kill(err)
|
|
if t.alive == 0 {
|
|
close(t.dead)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Kill puts the tomb in a dying state for the given reason,
|
|
// closes the Dying channel, and sets Alive to false.
|
|
//
|
|
// Althoguh Kill may be called multiple times, only the first
|
|
// non-nil error is recorded as the death reason.
|
|
//
|
|
// If reason is ErrDying, the previous reason isn't replaced
|
|
// even if nil. It's a runtime error to call Kill with ErrDying
|
|
// if t is not in a dying state.
|
|
func (t *Tomb) Kill(reason error) {
|
|
t.init()
|
|
t.m.Lock()
|
|
defer t.m.Unlock()
|
|
t.kill(reason)
|
|
}
|
|
|
|
func (t *Tomb) kill(reason error) {
|
|
if reason == ErrStillAlive {
|
|
panic("tomb: Kill with ErrStillAlive")
|
|
}
|
|
if reason == ErrDying {
|
|
if t.reason == ErrStillAlive {
|
|
panic("tomb: Kill with ErrDying while still alive")
|
|
}
|
|
return
|
|
}
|
|
if t.reason == ErrStillAlive {
|
|
t.reason = reason
|
|
close(t.dying)
|
|
return
|
|
}
|
|
if t.reason == nil {
|
|
t.reason = reason
|
|
return
|
|
}
|
|
}
|
|
|
|
// Killf calls the Kill method with an error built providing the received
|
|
// parameters to fmt.Errorf. The generated error is also returned.
|
|
func (t *Tomb) Killf(f string, a ...interface{}) error {
|
|
err := fmt.Errorf(f, a...)
|
|
t.Kill(err)
|
|
return err
|
|
}
|
|
|
|
// Err returns the death reason, or ErrStillAlive if the tomb
|
|
// is not in a dying or dead state.
|
|
func (t *Tomb) Err() (reason error) {
|
|
t.init()
|
|
t.m.Lock()
|
|
reason = t.reason
|
|
t.m.Unlock()
|
|
return
|
|
}
|
|
|
|
// Alive returns true if the tomb is not in a dying or dead state.
|
|
func (t *Tomb) Alive() bool {
|
|
return t.Err() == ErrStillAlive
|
|
}
|