blob: 33922c97fa80ba3ac5266de17a8191b18edc24aa [file] [log] [blame]
// 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 helps with clean goroutine termination.
//
// See the Tomb type for details.
package tomb
import (
"fmt"
"os"
"sync"
)
type nothing struct{}
// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
// and the reason for its death.
//
// The clean state of a Tomb informs that a goroutine is about to be
// created or already alive. Once Fatal or Fatalf is called with an
// argument that informs the reason for death, the goroutine is in
// a dying state and is expected to terminate soon. Right before the
// goroutine function or method returns, Done must be called to inform
// that the goroutine is indeed dead and about to stop running.
//
// A Tomb exposes Dying and Dead channels. These channels are closed
// when the Tomb state changes in the respective way. They enable
// explicit blocking until the state changes, and also to selectively
// unblock select statements accordingly.
//
// For background and a detailed example, see the following blog post:
//
// http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
//
type Tomb struct {
m sync.Mutex
Dying chan nothing
Dead chan nothing
reason os.Error
}
// New creates a new Tomb to track the lifecycle of a goroutine
// that is already alive or about to be created.
func New() *Tomb {
return &Tomb{Dying: make(chan nothing), Dead: make(chan nothing)}
}
// IsDying returns true if the goroutine is in a dying or already dead state.
func (t *Tomb) IsDying() bool {
select {
case <-t.Dying:
return true
default:
}
return false
}
// IsDead returns true if the goroutine is in a dead state.
func (t *Tomb) IsDead() bool {
select {
case <-t.Dead:
return true
default:
}
return false
}
// Wait blocks until the goroutine is in a dead state and returns the
// reason for its death. The reason may be nil.
func (t *Tomb) Wait() os.Error {
<-t.Dead
return t.reason
}
// Done flags the goroutine as dead, and should be called a single time
// right before the goroutine function or method returns.
// If the goroutine was not already in a dying state before Done is
// called, it will flagged as dying and dead at once.
func (t *Tomb) Done() {
t.Fatal(nil)
close(t.Dead)
}
// Fatal flags the goroutine as dying.
// The first non-nil reason parameter to Fatal or the first Fatalf-generated
// error is recorded as the reason for the goroutine death.
// This method may be safely called concurrently, and may be called both from
// within the goroutine and/or from outside to request the goroutine termination.
func (t *Tomb) Fatal(reason os.Error) {
t.m.Lock()
if t.reason == nil {
t.reason = reason
}
select {
case <-t.Dying:
default:
close(t.Dying)
}
t.m.Unlock()
}
// Fatalf works like Fatal, but builds the reason providing the received
// arguments to fmt.Errorf. The generated error is also returned.
func (t *Tomb) Fatalf(format string, args ...interface{}) os.Error {
err := fmt.Errorf(format, args...)
t.Fatal(err)
return err
}
// Err returns the reason for the goroutine death provided via Fatal or Fatalf.
func (t *Tomb) Err() (reason os.Error) {
t.m.Lock()
reason = t.reason
t.m.Unlock()
return
}