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
=== modified file 'tomb.go'
--- tomb.go 2012-01-18 00:28:24 +0000
+++ tomb.go 2012-03-06 17:23:19 +0000
@@ -34,15 +34,14 @@
34import (34import (
35 "errors"35 "errors"
36 "fmt"36 "fmt"
37
38 "sync"37 "sync"
39)38)
4039
41// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,40// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
42// and the reason for its death.41// and the reason for its death.
43//42//
44// The initial state of a Tomb informs that a goroutine is about to be43// The zero value of a Tomb assumes that a goroutine is about to be
45// created or already alive. Once Fatal or Fatalf is called with an44// created or already alive. Once Kill or Killf is called with an
46// argument that informs the reason for death, the goroutine is in45// argument that informs the reason for death, the goroutine is in
47// a dying state and is expected to terminate soon. Right before the46// a dying state and is expected to terminate soon. Right before the
48// goroutine function or method returns, Done must be called to inform47// goroutine function or method returns, Done must be called to inform
@@ -61,74 +60,85 @@
61 m sync.Mutex60 m sync.Mutex
62 dying chan struct{}61 dying chan struct{}
63 dead chan struct{}62 dead chan struct{}
64 Dying <-chan struct{}
65 Dead <-chan struct{}
66 reason error63 reason error
67}64}
6865
69// The Stop error is used as a reason for a goroutine to stop cleanly.66var ErrStillRunning = errors.New("tomb: goroutine is still running")
70var Stop = errors.New("clean stop")67
7168func (t *Tomb) init() {
72// New creates a new Tomb to track the lifecycle of a goroutine69 t.m.Lock()
73// that is already alive or about to be created.70 if t.dead == nil {
74func New() *Tomb {71 t.dead = make(chan struct{})
75 t := &Tomb{dying: make(chan struct{}), dead: make(chan struct{})}72 t.dying = make(chan struct{})
76 t.Dying = t.dying73 t.reason = ErrStillRunning
77 t.Dead = t.dead74 }
78 return t75 t.m.Unlock()
76}
77
78// Dead returns the channel that can be used to wait
79// until t.Done has been called.
80func (t *Tomb) Dead() <-chan struct{} {
81 t.init()
82 return t.dead
83}
84
85// Dying returns the channel that can be used to wait
86// until t.Kill or t.Done has been called.
87func (t *Tomb) Dying() <-chan struct{} {
88 t.init()
89 return t.dying
79}90}
8091
81// Wait blocks until the goroutine is in a dead state and returns the92// Wait blocks until the goroutine is in a dead state and returns the
82// reason for its death. If the reason is Stop, nil is returned.93// reason for its death.
83func (t *Tomb) Wait() error {94func (t *Tomb) Wait() error {
84 <-t.Dead95 t.init()
85 if t.reason == Stop {96 <-t.dead
86 return nil
87 }
88 return t.reason97 return t.reason
89}98}
9099
91// Done flags the goroutine as dead, and should be called a single time100// Done flags the goroutine as dead, and should be called a single time
92// right before the goroutine function or method returns.101// right before the goroutine function or method returns.
93// If the goroutine was not already in a dying state before Done is102// If the goroutine was not already in a dying state before Done is
94// called, it will be flagged as dying and dead at once with Stop as103// called, it will be flagged as dying and dead at once with no
95// the reason for death.104// error.
96func (t *Tomb) Done() {105func (t *Tomb) Done() {
97 t.Fatal(Stop)106 t.Kill(nil)
98 close(t.dead)107 close(t.dead)
99}108}
100109
101// Fatal flags the goroutine as dying for the given reason.110// Kill flags the goroutine as dying for the given reason.
102// Fatal may be called multiple times, but only the first error is111// Kill may be called multiple times, but only the first non-nil error is
103// recorded as the reason for termination.112// recorded as the reason for termination.
104// The Stop value may be used to terminate a goroutine cleanly.113func (t *Tomb) Kill(reason error) {
105func (t *Tomb) Fatal(reason error) {114 t.init()
106 if reason == nil {
107 panic("Fatal with nil reason")
108 }
109 t.m.Lock()115 t.m.Lock()
110 if t.reason == nil || t.reason == Stop {116 if t.reason == nil || t.reason == ErrStillRunning {
111 t.reason = reason117 t.reason = reason
112 }118 }
119 // If the receive on t.dying succeeds, then
120 // it can only be because we have already closed it.
121 // If it blocks, then we know that it needs to be closed.
113 select {122 select {
114 case <-t.Dying:123 case <-t.dying:
115 default:124 default:
116 close(t.dying)125 close(t.dying)
117 }126 }
118 t.m.Unlock()127 t.m.Unlock()
119}128}
120129
121// Fatalf works like Fatal, but builds the reason providing the received130// Killf works like Kill, but builds the reason providing the received
122// arguments to fmt.Errorf. The generated error is also returned.131// arguments to fmt.Errorf. The generated error is also returned.
123func (t *Tomb) Fatalf(format string, args ...interface{}) error {132func (t *Tomb) Killf(f string, a ...interface{}) error {
124 err := fmt.Errorf(format, args...)133 err := fmt.Errorf(f, a...)
125 t.Fatal(err)134 t.Kill(err)
126 return err135 return err
127}136}
128137
129// Err returns the reason for the goroutine death provided via Fatal138// Err returns the reason for the goroutine death provided via Kill
130// or Fatalf, or nil in case the goroutine is still alive.139// or Killf, or ErrStillRunning when the goroutine is still alive.
131func (t *Tomb) Err() (reason error) {140func (t *Tomb) Err() (reason error) {
141 t.init()
132 t.m.Lock()142 t.m.Lock()
133 reason = t.reason143 reason = t.reason
134 t.m.Unlock()144 t.m.Unlock()
135145
=== modified file 'tomb_test.go'
--- tomb_test.go 2012-01-18 00:28:24 +0000
+++ tomb_test.go 2012-03-06 17:23:19 +0000
@@ -4,48 +4,45 @@
4 "errors"4 "errors"
5 "launchpad.net/tomb"5 "launchpad.net/tomb"
6 "reflect"6 "reflect"
7
8 "testing"7 "testing"
9)8)
109
11func TestNewTomb(t *testing.T) {10func TestNewTomb(t *testing.T) {
12 tb := tomb.New()11 tb := new(tomb.Tomb)
13 testState(t, tb, false, false, nil)12 testState(t, tb, false, false, tomb.ErrStillRunning)
1413
15 tb.Done()14 tb.Done()
16 testState(t, tb, true, true, tomb.Stop)15 testState(t, tb, true, true, nil)
17}16}
1817
19func TestFatal(t *testing.T) {18func TestKill(t *testing.T) {
20 tb := tomb.New()19 // a nil reason flags the goroutine as dying
2120 tb := new(tomb.Tomb)
22 // the Stop reason flags the goroutine as dying21 tb.Kill(nil)
23 tb = tomb.New()22 testState(t, tb, true, false, nil)
24 tb.Fatal(tomb.Stop)23
25 testState(t, tb, true, false, tomb.Stop)24 // a non-nil reason now will override Kill
26
27 // a non-Stop reason now will override Stop
28 err := errors.New("some error")25 err := errors.New("some error")
29 tb.Fatal(err)26 tb.Kill(err)
30 testState(t, tb, true, false, err)27 testState(t, tb, true, false, err)
3128
32 // another non-nil reason won't replace the first one29 // another non-nil reason won't replace the first one
33 tb.Fatal(errors.New("ignore me"))30 tb.Kill(errors.New("ignore me"))
34 testState(t, tb, true, false, err)31 testState(t, tb, true, false, err)
3532
36 tb.Done()33 tb.Done()
37 testState(t, tb, true, true, err)34 testState(t, tb, true, true, err)
38}35}
3936
40func TestFatalf(t *testing.T) {37func TestKillf(t *testing.T) {
41 tb := tomb.New()38 tb := new(tomb.Tomb)
4239
43 err := errors.New("BOOM")40 err := errors.New("BOOM")
44 tb.Fatalf("BO%s", "OM")41 tb.Killf("BO%s", "OM")
45 testState(t, tb, true, false, err)42 testState(t, tb, true, false, err)
4643
47 // another non-Stop reason won't replace the first one44 // another non-nil reason won't replace the first one
48 tb.Fatalf("ignore me")45 tb.Killf("ignore me")
49 testState(t, tb, true, false, err)46 testState(t, tb, true, false, err)
5047
51 tb.Done()48 tb.Done()
@@ -54,7 +51,7 @@
5451
55func testState(t *testing.T, tb *tomb.Tomb, wantDying, wantDead bool, wantErr error) {52func testState(t *testing.T, tb *tomb.Tomb, wantDying, wantDead bool, wantErr error) {
56 select {53 select {
57 case <-tb.Dying:54 case <-tb.Dying():
58 if !wantDying {55 if !wantDying {
59 t.Error("<-Dying: should block")56 t.Error("<-Dying: should block")
60 }57 }
@@ -65,7 +62,7 @@
65 }62 }
66 seemsDead := false63 seemsDead := false
67 select {64 select {
68 case <-tb.Dead:65 case <-tb.Dead():
69 if !wantDead {66 if !wantDead {
70 t.Error("<-Dead: should block")67 t.Error("<-Dead: should block")
71 }68 }
@@ -80,11 +77,10 @@
80 }77 }
81 if wantDead && seemsDead {78 if wantDead && seemsDead {
82 waitErr := tb.Wait()79 waitErr := tb.Wait()
83 if wantErr == tomb.Stop {80 switch {
84 if waitErr != nil {81 case waitErr == tomb.ErrStillRunning:
85 t.Errorf("Wait: want nil, got %#v", waitErr)82 t.Errorf("Wait should not return ErrStillRunning")
86 }83 case !reflect.DeepEqual(waitErr, wantErr):
87 } else if !reflect.DeepEqual(waitErr, wantErr) {
88 t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr)84 t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr)
89 }85 }
90 }86 }

Subscribers

People subscribed via source and target branches

to all changes: