~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/tomb/tomb.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 helps with clean goroutine termination.
 
30
//
 
31
// See the Tomb type for details.
 
32
package tomb
 
33
 
 
34
import (
 
35
        "errors"
 
36
        "fmt"
 
37
        "sync"
 
38
)
 
39
 
 
40
// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
 
41
// and the reason for its death.
 
42
//
 
43
// The zero value of a Tomb assumes that a goroutine is about to be
 
44
// created or already alive. Once Kill or Killf is called with an
 
45
// argument that informs the reason for death, the goroutine is in
 
46
// a dying state and is expected to terminate soon. Right before the
 
47
// goroutine function or method returns, Done must be called to inform
 
48
// that the goroutine is indeed dead and about to stop running.
 
49
//
 
50
// A Tomb exposes Dying and Dead channels. These channels are closed
 
51
// when the Tomb state changes in the respective way. They enable
 
52
// explicit blocking until the state changes, and also to selectively
 
53
// unblock select statements accordingly.
 
54
//
 
55
// When the tomb state changes to dying and there's still logic going
 
56
// on within the goroutine, nested functions and methods may choose to
 
57
// return ErrDying as their error value, as this error won't alter the
 
58
// tomb state if provied to the Kill method. This is a convenient way to
 
59
// follow standard Go practices in the context of a dying tomb.
 
60
//
 
61
// For background and a detailed example, see the following blog post:
 
62
//
 
63
//   http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
 
64
//
 
65
// For a more complex code snippet demonstrating the use of multiple
 
66
// goroutines with a single Tomb, see:
 
67
//
 
68
//   http://play.golang.org/p/Xh7qWsDPZP
 
69
//
 
70
type Tomb struct {
 
71
        m      sync.Mutex
 
72
        dying  chan struct{}
 
73
        dead   chan struct{}
 
74
        reason error
 
75
}
 
76
 
 
77
var (
 
78
        ErrStillAlive = errors.New("tomb: still alive")
 
79
        ErrDying = errors.New("tomb: dying")
 
80
)
 
81
 
 
82
func (t *Tomb) init() {
 
83
        t.m.Lock()
 
84
        if t.dead == nil {
 
85
                t.dead = make(chan struct{})
 
86
                t.dying = make(chan struct{})
 
87
                t.reason = ErrStillAlive
 
88
        }
 
89
        t.m.Unlock()
 
90
}
 
91
 
 
92
// Dead returns the channel that can be used to wait
 
93
// until t.Done has been called.
 
94
func (t *Tomb) Dead() <-chan struct{} {
 
95
        t.init()
 
96
        return t.dead
 
97
}
 
98
 
 
99
// Dying returns the channel that can be used to wait
 
100
// until t.Kill or t.Done has been called.
 
101
func (t *Tomb) Dying() <-chan struct{} {
 
102
        t.init()
 
103
        return t.dying
 
104
}
 
105
 
 
106
// Wait blocks until the goroutine is in a dead state and returns the
 
107
// reason for its death.
 
108
func (t *Tomb) Wait() error {
 
109
        t.init()
 
110
        <-t.dead
 
111
        return t.reason
 
112
}
 
113
 
 
114
// Done flags the goroutine as dead, and should be called a single time
 
115
// right before the goroutine function or method returns.
 
116
// If the goroutine was not already in a dying state before Done is
 
117
// called, it will be flagged as dying and dead at once with no
 
118
// error.
 
119
func (t *Tomb) Done() {
 
120
        t.Kill(nil)
 
121
        close(t.dead)
 
122
}
 
123
 
 
124
// Kill flags the goroutine as dying for the given reason.
 
125
// Kill may be called multiple times, but only the first
 
126
// non-nil error is recorded as the reason for termination.
 
127
//
 
128
// If reason is ErrDying, the previous reason isn't replaced
 
129
// even if it is nil. It's a runtime error to call Kill with
 
130
// ErrDying if t is not in a dying state.
 
131
func (t *Tomb) Kill(reason error) {
 
132
        t.init()
 
133
        t.m.Lock()
 
134
        defer t.m.Unlock()
 
135
        if reason == ErrDying {
 
136
                if t.reason == ErrStillAlive {
 
137
                        panic("tomb: Kill with ErrDying while still alive")
 
138
                }
 
139
                return
 
140
        }
 
141
        if t.reason == nil || t.reason == ErrStillAlive {
 
142
                t.reason = reason
 
143
        }
 
144
        // If the receive on t.dying succeeds, then
 
145
        // it can only be because we have already closed it.
 
146
        // If it blocks, then we know that it needs to be closed.
 
147
        select {
 
148
        case <-t.dying:
 
149
        default:
 
150
                close(t.dying)
 
151
        }
 
152
}
 
153
 
 
154
// Killf works like Kill, but builds the reason providing the received
 
155
// arguments to fmt.Errorf. The generated error is also returned.
 
156
func (t *Tomb) Killf(f string, a ...interface{}) error {
 
157
        err := fmt.Errorf(f, a...)
 
158
        t.Kill(err)
 
159
        return err
 
160
}
 
161
 
 
162
// Err returns the reason for the goroutine death provided via Kill
 
163
// or Killf, or ErrStillAlive when the goroutine is still alive.
 
164
func (t *Tomb) Err() (reason error) {
 
165
        t.init()
 
166
        t.m.Lock()
 
167
        reason = t.reason
 
168
        t.m.Unlock()
 
169
        return
 
170
}