tomb.go

  1// Copyright (c) 2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
  2// 
  3// All rights reserved.
  4// 
  5// Redistribution and use in source and binary forms, with or without
  6// modification, are permitted provided that the following conditions are met:
  7// 
  8//     * Redistributions of source code must retain the above copyright notice,
  9//       this list of conditions and the following disclaimer.
 10//     * Redistributions in binary form must reproduce the above copyright notice,
 11//       this list of conditions and the following disclaimer in the documentation
 12//       and/or other materials provided with the distribution.
 13//     * Neither the name of the copyright holder nor the names of its
 14//       contributors may be used to endorse or promote products derived from
 15//       this software without specific prior written permission.
 16// 
 17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28
 29// The tomb package offers a conventional API for clean goroutine termination.
 30//
 31// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
 32// and the reason for its death.
 33//
 34// The zero value of a Tomb assumes that a goroutine is about to be
 35// created or already alive. Once Kill or Killf is called with an
 36// argument that informs the reason for death, the goroutine is in
 37// a dying state and is expected to terminate soon. Right before the
 38// goroutine function or method returns, Done must be called to inform
 39// that the goroutine is indeed dead and about to stop running.
 40//
 41// A Tomb exposes Dying and Dead channels. These channels are closed
 42// when the Tomb state changes in the respective way. They enable
 43// explicit blocking until the state changes, and also to selectively
 44// unblock select statements accordingly.
 45//
 46// When the tomb state changes to dying and there's still logic going
 47// on within the goroutine, nested functions and methods may choose to
 48// return ErrDying as their error value, as this error won't alter the
 49// tomb state if provided to the Kill method. This is a convenient way to
 50// follow standard Go practices in the context of a dying tomb.
 51//
 52// For background and a detailed example, see the following blog post:
 53//
 54//   http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
 55//
 56// For a more complex code snippet demonstrating the use of multiple
 57// goroutines with a single Tomb, see:
 58//
 59//   http://play.golang.org/p/Xh7qWsDPZP
 60//
 61package tomb
 62
 63import (
 64	"errors"
 65	"fmt"
 66	"sync"
 67)
 68
 69// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
 70// and the reason for its death.
 71//
 72// See the package documentation for details.
 73type Tomb struct {
 74	m      sync.Mutex
 75	dying  chan struct{}
 76	dead   chan struct{}
 77	reason error
 78}
 79
 80var (
 81	ErrStillAlive = errors.New("tomb: still alive")
 82	ErrDying = errors.New("tomb: dying")
 83)
 84
 85func (t *Tomb) init() {
 86	t.m.Lock()
 87	if t.dead == nil {
 88		t.dead = make(chan struct{})
 89		t.dying = make(chan struct{})
 90		t.reason = ErrStillAlive
 91	}
 92	t.m.Unlock()
 93}
 94
 95// Dead returns the channel that can be used to wait
 96// until t.Done has been called.
 97func (t *Tomb) Dead() <-chan struct{} {
 98	t.init()
 99	return t.dead
100}
101
102// Dying returns the channel that can be used to wait
103// until t.Kill or t.Done has been called.
104func (t *Tomb) Dying() <-chan struct{} {
105	t.init()
106	return t.dying
107}
108
109// Wait blocks until the goroutine is in a dead state and returns the
110// reason for its death.
111func (t *Tomb) Wait() error {
112	t.init()
113	<-t.dead
114	t.m.Lock()
115	reason := t.reason
116	t.m.Unlock()
117	return reason
118}
119
120// Done flags the goroutine as dead, and should be called a single time
121// right before the goroutine function or method returns.
122// If the goroutine was not already in a dying state before Done is
123// called, it will be flagged as dying and dead at once with no
124// error.
125func (t *Tomb) Done() {
126	t.Kill(nil)
127	close(t.dead)
128}
129
130// Kill flags the goroutine as dying for the given reason.
131// Kill may be called multiple times, but only the first
132// non-nil error is recorded as the reason for termination.
133//
134// If reason is ErrDying, the previous reason isn't replaced
135// even if it is nil. It's a runtime error to call Kill with
136// ErrDying if t is not in a dying state.
137func (t *Tomb) Kill(reason error) {
138	t.init()
139	t.m.Lock()
140	defer t.m.Unlock()
141	if reason == ErrDying {
142		if t.reason == ErrStillAlive {
143			panic("tomb: Kill with ErrDying while still alive")
144		}
145		return
146	}
147	if t.reason == nil || t.reason == ErrStillAlive {
148		t.reason = reason
149	}
150	// If the receive on t.dying succeeds, then
151	// it can only be because we have already closed it.
152	// If it blocks, then we know that it needs to be closed.
153	select {
154	case <-t.dying:
155	default:
156		close(t.dying)
157	}
158}
159
160// Killf works like Kill, but builds the reason providing the received
161// arguments to fmt.Errorf. The generated error is also returned.
162func (t *Tomb) Killf(f string, a ...interface{}) error {
163	err := fmt.Errorf(f, a...)
164	t.Kill(err)
165	return err
166}
167
168// Err returns the reason for the goroutine death provided via Kill
169// or Killf, or ErrStillAlive when the goroutine is still alive.
170func (t *Tomb) Err() (reason error) {
171	t.init()
172	t.m.Lock()
173	reason = t.reason
174	t.m.Unlock()
175	return
176}