Merge lp:~rogpeppe/tomb/update-api into lp:tomb

Proposed by Roger Peppe
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
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Pending
Review via email: mp+96158@code.launchpad.net

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.

https://codereview.appspot.com/5755055/

To post a comment you must log in.
Revision history for this message
Roger Peppe (rogpeppe) wrote :

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

Revision history for this message
Roger Peppe (rogpeppe) wrote :
lp:~rogpeppe/tomb/update-api updated
15. By Roger Peppe

remove references to stop

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/5755055/diff/3001/tomb.go
File tomb.go (right):

https://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode44
tomb.go:44: // created or already alive. Once Kill is called with an
s/Kill/Kill or Killf/, as before please.

https://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode80
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://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode130
tomb.go:130: reason = errCleanKill
nil itself is errCleanKill. We don't need a second way to flag this.

https://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode136
tomb.go:136: if t.state == running {
if t.reason != ErrStillRunning

https://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode162
tomb.go:162: }
The switch is unnecessary with the suggested change. Just return
t.reason at all times.

https://codereview.appspot.com/5755055/

Revision history for this message
Roger Peppe (rogpeppe) wrote :
lp:~rogpeppe/tomb/update-api updated
16. By Roger Peppe

simplifications for review

Revision history for this message
Roger Peppe (rogpeppe) wrote :
lp:~rogpeppe/tomb/update-api updated
17. By Roger Peppe

modify comment

Revision history for this message
Roger Peppe (rogpeppe) wrote :

much better with those changes, thanks.
PTAL.

https://codereview.appspot.com/5755055/diff/3001/tomb.go
File tomb.go (right):

https://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode44
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://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode80
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://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode130
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://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode136
tomb.go:136: if t.state == running {
On 2012/03/06 16:07:35, niemeyer wrote:
> if t.reason != ErrStillRunning

Done.

https://codereview.appspot.com/5755055/diff/3001/tomb.go#newcode162
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.

https://codereview.appspot.com/5755055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

A couple of trivials, and should be ready to submit.

https://codereview.appspot.com/5755055/diff/6003/tomb.go
File tomb.go (right):

https://codereview.appspot.com/5755055/diff/6003/tomb.go#newcode106
tomb.go:106: t.init()
init isn't needed here.

https://codereview.appspot.com/5755055/diff/6003/tomb_test.go
File tomb_test.go (right):

https://codereview.appspot.com/5755055/diff/6003/tomb_test.go#newcode19
tomb_test.go:19: // the Kill reason flags the goroutine as dying
s/Kill/nil/

https://codereview.appspot.com/5755055/diff/6003/tomb_test.go#newcode24
tomb_test.go:24: // a non-Kill reason now will override Kill
s/Kill/nil/g

https://codereview.appspot.com/5755055/

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :

https://codereview.appspot.com/5755055/diff/6003/tomb.go
File tomb.go (right):

https://codereview.appspot.com/5755055/diff/6003/tomb.go#newcode106
tomb.go:106: t.init()
On 2012/03/06 17:06:55, niemeyer wrote:
> init isn't needed here.

Done.

https://codereview.appspot.com/5755055/diff/6003/tomb_test.go
File tomb_test.go (right):

https://codereview.appspot.com/5755055/diff/6003/tomb_test.go#newcode19
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://codereview.appspot.com/5755055/diff/6003/tomb_test.go#newcode24
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.

https://codereview.appspot.com/5755055/

lp:~rogpeppe/tomb/update-api updated
18. By Roger Peppe

trivials for review

Revision history for this message
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://codereview.appspot.com/5755055

https://codereview.appspot.com/5755055/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }

Subscribers

People subscribed via source and target branches

to all changes: