Status: | Merged |
---|---|
Merged at revision: | 13 |
Proposed branch: | lp:~rogpeppe/tomb/update-api |
Merge into: | lp:tomb |
Diff against target: |
246 lines (+71/-65) 2 files modified
tomb.go (+48/-38) tomb_test.go (+23/-27) |
To merge this branch: | bzr merge lp:~rogpeppe/tomb/update-api |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Pending | ||
Review via email: mp+96158@code.launchpad.net |
Commit message
Description of the change
tomb: update API
- Rename Fatal and Fatalf to Kill and Killf.
- The Stop error value is gone. Use nil now.
- Err will now return ErrStillRunning if the goroutine isn't dead
yet, which means a nil return reflects an actual nil Kill call.
- Make zero-value of Tomb good to use, and as a consequence, make
Dead and Dying into methods rather than fields.
Roger Peppe (rogpeppe) wrote : | # |
Roger Peppe (rogpeppe) wrote : | # |
Please take a look.
- 15. By Roger Peppe
-
remove references to stop
Gustavo Niemeyer (niemeyer) wrote : | # |
https:/
File tomb.go (right):
https:/
tomb.go:44: // created or already alive. Once Kill is called with an
s/Kill/Kill or Killf/, as before please.
https:/
tomb.go:80: if t.state == uninitialized {
if t.dead == nil {
t.dead = make(...)
t.dying = make(...)
t.reason = ErrStillRunning
}
We don't need "state".
https:/
tomb.go:130: reason = errCleanKill
nil itself is errCleanKill. We don't need a second way to flag this.
https:/
tomb.go:136: if t.state == running {
if t.reason != ErrStillRunning
https:/
tomb.go:162: }
The switch is unnecessary with the suggested change. Just return
t.reason at all times.
Roger Peppe (rogpeppe) wrote : | # |
Please take a look.
- 16. By Roger Peppe
-
simplifications for review
Roger Peppe (rogpeppe) wrote : | # |
Please take a look.
- 17. By Roger Peppe
-
modify comment
Roger Peppe (rogpeppe) wrote : | # |
much better with those changes, thanks.
PTAL.
https:/
File tomb.go (right):
https:/
tomb.go:44: // created or already alive. Once Kill is called with an
On 2012/03/06 16:07:35, niemeyer wrote:
> s/Kill/Kill or Killf/, as before please.
Done.
https:/
tomb.go:80: if t.state == uninitialized {
On 2012/03/06 16:07:35, niemeyer wrote:
> if t.dead == nil {
> t.dead = make(...)
> t.dying = make(...)
> t.reason = ErrStillRunning
> }
> We don't need "state".
Done.
https:/
tomb.go:130: reason = errCleanKill
On 2012/03/06 16:07:35, niemeyer wrote:
> nil itself is errCleanKill. We don't need a second way to flag this.
Done.
https:/
tomb.go:136: if t.state == running {
On 2012/03/06 16:07:35, niemeyer wrote:
> if t.reason != ErrStillRunning
Done.
https:/
tomb.go:162: }
On 2012/03/06 16:07:35, niemeyer wrote:
> The switch is unnecessary with the suggested change. Just return
t.reason at all
> times.
Done.
Gustavo Niemeyer (niemeyer) wrote : | # |
A couple of trivials, and should be ready to submit.
https:/
File tomb.go (right):
https:/
tomb.go:106: t.init()
init isn't needed here.
https:/
File tomb_test.go (right):
https:/
tomb_test.go:19: // the Kill reason flags the goroutine as dying
s/Kill/nil/
https:/
tomb_test.go:24: // a non-Kill reason now will override Kill
s/Kill/nil/g
Roger Peppe (rogpeppe) wrote : | # |
Please take a look.
Roger Peppe (rogpeppe) wrote : | # |
https:/
File tomb.go (right):
https:/
tomb.go:106: t.init()
On 2012/03/06 17:06:55, niemeyer wrote:
> init isn't needed here.
Done.
https:/
File tomb_test.go (right):
https:/
tomb_test.go:19: // the Kill reason flags the goroutine as dying
On 2012/03/06 17:06:55, niemeyer wrote:
> s/Kill/nil/
Done.
https:/
tomb_test.go:24: // a non-Kill reason now will override Kill
On 2012/03/06 17:06:55, niemeyer wrote:
> s/Kill/nil/g
Done.
- 18. By Roger Peppe
-
trivials for review
Gustavo Niemeyer (niemeyer) wrote : | # |
*** Submitted:
tomb: update API
- Rename Fatal and Fatalf to Kill and Killf.
- The Stop error value is gone. Use nil now.
- Err will now return ErrStillRunning if the goroutine isn't dead
yet, which means a nil return reflects an actual nil Kill call.
- Make zero-value of Tomb good to use, and as a consequence, make
Dead and Dying into methods rather than fields.
R=niemeyer
CC=
https:/
Preview Diff
1 | === modified file 'tomb.go' |
2 | --- tomb.go 2012-01-18 00:28:24 +0000 |
3 | +++ tomb.go 2012-03-06 17:23:19 +0000 |
4 | @@ -34,15 +34,14 @@ |
5 | import ( |
6 | "errors" |
7 | "fmt" |
8 | - |
9 | "sync" |
10 | ) |
11 | |
12 | // A Tomb tracks the lifecycle of a goroutine as alive, dying or dead, |
13 | // and the reason for its death. |
14 | // |
15 | -// The initial state of a Tomb informs that a goroutine is about to be |
16 | -// created or already alive. Once Fatal or Fatalf is called with an |
17 | +// The zero value of a Tomb assumes that a goroutine is about to be |
18 | +// created or already alive. Once Kill or Killf is called with an |
19 | // argument that informs the reason for death, the goroutine is in |
20 | // a dying state and is expected to terminate soon. Right before the |
21 | // goroutine function or method returns, Done must be called to inform |
22 | @@ -61,74 +60,85 @@ |
23 | m sync.Mutex |
24 | dying chan struct{} |
25 | dead chan struct{} |
26 | - Dying <-chan struct{} |
27 | - Dead <-chan struct{} |
28 | reason error |
29 | } |
30 | |
31 | -// The Stop error is used as a reason for a goroutine to stop cleanly. |
32 | -var Stop = errors.New("clean stop") |
33 | - |
34 | -// New creates a new Tomb to track the lifecycle of a goroutine |
35 | -// that is already alive or about to be created. |
36 | -func New() *Tomb { |
37 | - t := &Tomb{dying: make(chan struct{}), dead: make(chan struct{})} |
38 | - t.Dying = t.dying |
39 | - t.Dead = t.dead |
40 | - return t |
41 | +var ErrStillRunning = errors.New("tomb: goroutine is still running") |
42 | + |
43 | +func (t *Tomb) init() { |
44 | + t.m.Lock() |
45 | + if t.dead == nil { |
46 | + t.dead = make(chan struct{}) |
47 | + t.dying = make(chan struct{}) |
48 | + t.reason = ErrStillRunning |
49 | + } |
50 | + t.m.Unlock() |
51 | +} |
52 | + |
53 | +// Dead returns the channel that can be used to wait |
54 | +// until t.Done has been called. |
55 | +func (t *Tomb) Dead() <-chan struct{} { |
56 | + t.init() |
57 | + return t.dead |
58 | +} |
59 | + |
60 | +// Dying returns the channel that can be used to wait |
61 | +// until t.Kill or t.Done has been called. |
62 | +func (t *Tomb) Dying() <-chan struct{} { |
63 | + t.init() |
64 | + return t.dying |
65 | } |
66 | |
67 | // Wait blocks until the goroutine is in a dead state and returns the |
68 | -// reason for its death. If the reason is Stop, nil is returned. |
69 | +// reason for its death. |
70 | func (t *Tomb) Wait() error { |
71 | - <-t.Dead |
72 | - if t.reason == Stop { |
73 | - return nil |
74 | - } |
75 | + t.init() |
76 | + <-t.dead |
77 | return t.reason |
78 | } |
79 | |
80 | // Done flags the goroutine as dead, and should be called a single time |
81 | // right before the goroutine function or method returns. |
82 | // If the goroutine was not already in a dying state before Done is |
83 | -// called, it will be flagged as dying and dead at once with Stop as |
84 | -// the reason for death. |
85 | +// called, it will be flagged as dying and dead at once with no |
86 | +// error. |
87 | func (t *Tomb) Done() { |
88 | - t.Fatal(Stop) |
89 | + t.Kill(nil) |
90 | close(t.dead) |
91 | } |
92 | |
93 | -// Fatal flags the goroutine as dying for the given reason. |
94 | -// Fatal may be called multiple times, but only the first error is |
95 | +// Kill flags the goroutine as dying for the given reason. |
96 | +// Kill may be called multiple times, but only the first non-nil error is |
97 | // recorded as the reason for termination. |
98 | -// The Stop value may be used to terminate a goroutine cleanly. |
99 | -func (t *Tomb) Fatal(reason error) { |
100 | - if reason == nil { |
101 | - panic("Fatal with nil reason") |
102 | - } |
103 | +func (t *Tomb) Kill(reason error) { |
104 | + t.init() |
105 | t.m.Lock() |
106 | - if t.reason == nil || t.reason == Stop { |
107 | + if t.reason == nil || t.reason == ErrStillRunning { |
108 | t.reason = reason |
109 | } |
110 | + // If the receive on t.dying succeeds, then |
111 | + // it can only be because we have already closed it. |
112 | + // If it blocks, then we know that it needs to be closed. |
113 | select { |
114 | - case <-t.Dying: |
115 | + case <-t.dying: |
116 | default: |
117 | close(t.dying) |
118 | } |
119 | t.m.Unlock() |
120 | } |
121 | |
122 | -// Fatalf works like Fatal, but builds the reason providing the received |
123 | +// Killf works like Kill, but builds the reason providing the received |
124 | // arguments to fmt.Errorf. The generated error is also returned. |
125 | -func (t *Tomb) Fatalf(format string, args ...interface{}) error { |
126 | - err := fmt.Errorf(format, args...) |
127 | - t.Fatal(err) |
128 | +func (t *Tomb) Killf(f string, a ...interface{}) error { |
129 | + err := fmt.Errorf(f, a...) |
130 | + t.Kill(err) |
131 | return err |
132 | } |
133 | |
134 | -// Err returns the reason for the goroutine death provided via Fatal |
135 | -// or Fatalf, or nil in case the goroutine is still alive. |
136 | +// Err returns the reason for the goroutine death provided via Kill |
137 | +// or Killf, or ErrStillRunning when the goroutine is still alive. |
138 | func (t *Tomb) Err() (reason error) { |
139 | + t.init() |
140 | t.m.Lock() |
141 | reason = t.reason |
142 | t.m.Unlock() |
143 | |
144 | === modified file 'tomb_test.go' |
145 | --- tomb_test.go 2012-01-18 00:28:24 +0000 |
146 | +++ tomb_test.go 2012-03-06 17:23:19 +0000 |
147 | @@ -4,48 +4,45 @@ |
148 | "errors" |
149 | "launchpad.net/tomb" |
150 | "reflect" |
151 | - |
152 | "testing" |
153 | ) |
154 | |
155 | func TestNewTomb(t *testing.T) { |
156 | - tb := tomb.New() |
157 | - testState(t, tb, false, false, nil) |
158 | + tb := new(tomb.Tomb) |
159 | + testState(t, tb, false, false, tomb.ErrStillRunning) |
160 | |
161 | tb.Done() |
162 | - testState(t, tb, true, true, tomb.Stop) |
163 | + testState(t, tb, true, true, nil) |
164 | } |
165 | |
166 | -func TestFatal(t *testing.T) { |
167 | - tb := tomb.New() |
168 | - |
169 | - // the Stop reason flags the goroutine as dying |
170 | - tb = tomb.New() |
171 | - tb.Fatal(tomb.Stop) |
172 | - testState(t, tb, true, false, tomb.Stop) |
173 | - |
174 | - // a non-Stop reason now will override Stop |
175 | +func TestKill(t *testing.T) { |
176 | + // a nil reason flags the goroutine as dying |
177 | + tb := new(tomb.Tomb) |
178 | + tb.Kill(nil) |
179 | + testState(t, tb, true, false, nil) |
180 | + |
181 | + // a non-nil reason now will override Kill |
182 | err := errors.New("some error") |
183 | - tb.Fatal(err) |
184 | + tb.Kill(err) |
185 | testState(t, tb, true, false, err) |
186 | |
187 | // another non-nil reason won't replace the first one |
188 | - tb.Fatal(errors.New("ignore me")) |
189 | + tb.Kill(errors.New("ignore me")) |
190 | testState(t, tb, true, false, err) |
191 | |
192 | tb.Done() |
193 | testState(t, tb, true, true, err) |
194 | } |
195 | |
196 | -func TestFatalf(t *testing.T) { |
197 | - tb := tomb.New() |
198 | +func TestKillf(t *testing.T) { |
199 | + tb := new(tomb.Tomb) |
200 | |
201 | err := errors.New("BOOM") |
202 | - tb.Fatalf("BO%s", "OM") |
203 | + tb.Killf("BO%s", "OM") |
204 | testState(t, tb, true, false, err) |
205 | |
206 | - // another non-Stop reason won't replace the first one |
207 | - tb.Fatalf("ignore me") |
208 | + // another non-nil reason won't replace the first one |
209 | + tb.Killf("ignore me") |
210 | testState(t, tb, true, false, err) |
211 | |
212 | tb.Done() |
213 | @@ -54,7 +51,7 @@ |
214 | |
215 | func testState(t *testing.T, tb *tomb.Tomb, wantDying, wantDead bool, wantErr error) { |
216 | select { |
217 | - case <-tb.Dying: |
218 | + case <-tb.Dying(): |
219 | if !wantDying { |
220 | t.Error("<-Dying: should block") |
221 | } |
222 | @@ -65,7 +62,7 @@ |
223 | } |
224 | seemsDead := false |
225 | select { |
226 | - case <-tb.Dead: |
227 | + case <-tb.Dead(): |
228 | if !wantDead { |
229 | t.Error("<-Dead: should block") |
230 | } |
231 | @@ -80,11 +77,10 @@ |
232 | } |
233 | if wantDead && seemsDead { |
234 | waitErr := tb.Wait() |
235 | - if wantErr == tomb.Stop { |
236 | - if waitErr != nil { |
237 | - t.Errorf("Wait: want nil, got %#v", waitErr) |
238 | - } |
239 | - } else if !reflect.DeepEqual(waitErr, wantErr) { |
240 | + switch { |
241 | + case waitErr == tomb.ErrStillRunning: |
242 | + t.Errorf("Wait should not return ErrStillRunning") |
243 | + case !reflect.DeepEqual(waitErr, wantErr): |
244 | t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr) |
245 | } |
246 | } |
Reviewers: mp+96158_ code.launchpad. net,
Message:
Please take a look.
Description:
As discussed on IRC:
Rename Fatal and Fatalf to Kill and Killf.
Make zero-value of Tomb good to use, and as a consequence, make Dead
and Dying into methods rather than fields.
https:/ /code.launchpad .net/~rogpeppe/ tomb/update- api/+merge/ 96158
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/5755055/
Affected files:
M tomb.go
M tomb_test.go