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 | 34 | import ( | 34 | import ( |
6 | 35 | "errors" | 35 | "errors" |
7 | 36 | "fmt" | 36 | "fmt" |
8 | 37 | |||
9 | 38 | "sync" | 37 | "sync" |
10 | 39 | ) | 38 | ) |
11 | 40 | 39 | ||
12 | 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, |
13 | 42 | // and the reason for its death. | 41 | // and the reason for its death. |
14 | 43 | // | 42 | // |
17 | 44 | // The initial state of a Tomb informs that a goroutine is about to be | 43 | // The zero value of a Tomb assumes that a goroutine is about to be |
18 | 45 | // created or already alive. Once Fatal or Fatalf is called with an | 44 | // created or already alive. Once Kill or Killf is called with an |
19 | 46 | // argument that informs the reason for death, the goroutine is in | 45 | // argument that informs the reason for death, the goroutine is in |
20 | 47 | // a dying state and is expected to terminate soon. Right before the | 46 | // a dying state and is expected to terminate soon. Right before the |
21 | 48 | // goroutine function or method returns, Done must be called to inform | 47 | // goroutine function or method returns, Done must be called to inform |
22 | @@ -61,74 +60,85 @@ | |||
23 | 61 | m sync.Mutex | 60 | m sync.Mutex |
24 | 62 | dying chan struct{} | 61 | dying chan struct{} |
25 | 63 | dead chan struct{} | 62 | dead chan struct{} |
26 | 64 | Dying <-chan struct{} | ||
27 | 65 | Dead <-chan struct{} | ||
28 | 66 | reason error | 63 | reason error |
29 | 67 | } | 64 | } |
30 | 68 | 65 | ||
41 | 69 | // The Stop error is used as a reason for a goroutine to stop cleanly. | 66 | var ErrStillRunning = errors.New("tomb: goroutine is still running") |
42 | 70 | var Stop = errors.New("clean stop") | 67 | |
43 | 71 | 68 | func (t *Tomb) init() { | |
44 | 72 | // New creates a new Tomb to track the lifecycle of a goroutine | 69 | t.m.Lock() |
45 | 73 | // that is already alive or about to be created. | 70 | if t.dead == nil { |
46 | 74 | func New() *Tomb { | 71 | t.dead = make(chan struct{}) |
47 | 75 | t := &Tomb{dying: make(chan struct{}), dead: make(chan struct{})} | 72 | t.dying = make(chan struct{}) |
48 | 76 | t.Dying = t.dying | 73 | t.reason = ErrStillRunning |
49 | 77 | t.Dead = t.dead | 74 | } |
50 | 78 | return t | 75 | t.m.Unlock() |
51 | 76 | } | ||
52 | 77 | |||
53 | 78 | // Dead returns the channel that can be used to wait | ||
54 | 79 | // until t.Done has been called. | ||
55 | 80 | func (t *Tomb) Dead() <-chan struct{} { | ||
56 | 81 | t.init() | ||
57 | 82 | return t.dead | ||
58 | 83 | } | ||
59 | 84 | |||
60 | 85 | // Dying returns the channel that can be used to wait | ||
61 | 86 | // until t.Kill or t.Done has been called. | ||
62 | 87 | func (t *Tomb) Dying() <-chan struct{} { | ||
63 | 88 | t.init() | ||
64 | 89 | return t.dying | ||
65 | 79 | } | 90 | } |
66 | 80 | 91 | ||
67 | 81 | // Wait blocks until the goroutine is in a dead state and returns the | 92 | // Wait blocks until the goroutine is in a dead state and returns the |
69 | 82 | // reason for its death. If the reason is Stop, nil is returned. | 93 | // reason for its death. |
70 | 83 | func (t *Tomb) Wait() error { | 94 | func (t *Tomb) Wait() error { |
75 | 84 | <-t.Dead | 95 | t.init() |
76 | 85 | if t.reason == Stop { | 96 | <-t.dead |
73 | 86 | return nil | ||
74 | 87 | } | ||
77 | 88 | return t.reason | 97 | return t.reason |
78 | 89 | } | 98 | } |
79 | 90 | 99 | ||
80 | 91 | // Done flags the goroutine as dead, and should be called a single time | 100 | // Done flags the goroutine as dead, and should be called a single time |
81 | 92 | // right before the goroutine function or method returns. | 101 | // right before the goroutine function or method returns. |
82 | 93 | // If the goroutine was not already in a dying state before Done is | 102 | // If the goroutine was not already in a dying state before Done is |
85 | 94 | // called, it will be flagged as dying and dead at once with Stop as | 103 | // called, it will be flagged as dying and dead at once with no |
86 | 95 | // the reason for death. | 104 | // error. |
87 | 96 | func (t *Tomb) Done() { | 105 | func (t *Tomb) Done() { |
89 | 97 | t.Fatal(Stop) | 106 | t.Kill(nil) |
90 | 98 | close(t.dead) | 107 | close(t.dead) |
91 | 99 | } | 108 | } |
92 | 100 | 109 | ||
95 | 101 | // Fatal flags the goroutine as dying for the given reason. | 110 | // Kill flags the goroutine as dying for the given reason. |
96 | 102 | // Fatal may be called multiple times, but only the first error is | 111 | // Kill may be called multiple times, but only the first non-nil error is |
97 | 103 | // recorded as the reason for termination. | 112 | // recorded as the reason for termination. |
103 | 104 | // The Stop value may be used to terminate a goroutine cleanly. | 113 | func (t *Tomb) Kill(reason error) { |
104 | 105 | func (t *Tomb) Fatal(reason error) { | 114 | t.init() |
100 | 106 | if reason == nil { | ||
101 | 107 | panic("Fatal with nil reason") | ||
102 | 108 | } | ||
105 | 109 | t.m.Lock() | 115 | t.m.Lock() |
107 | 110 | if t.reason == nil || t.reason == Stop { | 116 | if t.reason == nil || t.reason == ErrStillRunning { |
108 | 111 | t.reason = reason | 117 | t.reason = reason |
109 | 112 | } | 118 | } |
110 | 119 | // If the receive on t.dying succeeds, then | ||
111 | 120 | // it can only be because we have already closed it. | ||
112 | 121 | // If it blocks, then we know that it needs to be closed. | ||
113 | 113 | select { | 122 | select { |
115 | 114 | case <-t.Dying: | 123 | case <-t.dying: |
116 | 115 | default: | 124 | default: |
117 | 116 | close(t.dying) | 125 | close(t.dying) |
118 | 117 | } | 126 | } |
119 | 118 | t.m.Unlock() | 127 | t.m.Unlock() |
120 | 119 | } | 128 | } |
121 | 120 | 129 | ||
123 | 121 | // Fatalf works like Fatal, but builds the reason providing the received | 130 | // Killf works like Kill, but builds the reason providing the received |
124 | 122 | // arguments to fmt.Errorf. The generated error is also returned. | 131 | // arguments to fmt.Errorf. The generated error is also returned. |
128 | 123 | func (t *Tomb) Fatalf(format string, args ...interface{}) error { | 132 | func (t *Tomb) Killf(f string, a ...interface{}) error { |
129 | 124 | err := fmt.Errorf(format, args...) | 133 | err := fmt.Errorf(f, a...) |
130 | 125 | t.Fatal(err) | 134 | t.Kill(err) |
131 | 126 | return err | 135 | return err |
132 | 127 | } | 136 | } |
133 | 128 | 137 | ||
136 | 129 | // Err returns the reason for the goroutine death provided via Fatal | 138 | // Err returns the reason for the goroutine death provided via Kill |
137 | 130 | // or Fatalf, or nil in case the goroutine is still alive. | 139 | // or Killf, or ErrStillRunning when the goroutine is still alive. |
138 | 131 | func (t *Tomb) Err() (reason error) { | 140 | func (t *Tomb) Err() (reason error) { |
139 | 141 | t.init() | ||
140 | 132 | t.m.Lock() | 142 | t.m.Lock() |
141 | 133 | reason = t.reason | 143 | reason = t.reason |
142 | 134 | t.m.Unlock() | 144 | t.m.Unlock() |
143 | 135 | 145 | ||
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 | 4 | "errors" | 4 | "errors" |
149 | 5 | "launchpad.net/tomb" | 5 | "launchpad.net/tomb" |
150 | 6 | "reflect" | 6 | "reflect" |
151 | 7 | |||
152 | 8 | "testing" | 7 | "testing" |
153 | 9 | ) | 8 | ) |
154 | 10 | 9 | ||
155 | 11 | func TestNewTomb(t *testing.T) { | 10 | func TestNewTomb(t *testing.T) { |
158 | 12 | tb := tomb.New() | 11 | tb := new(tomb.Tomb) |
159 | 13 | testState(t, tb, false, false, nil) | 12 | testState(t, tb, false, false, tomb.ErrStillRunning) |
160 | 14 | 13 | ||
161 | 15 | tb.Done() | 14 | tb.Done() |
163 | 16 | testState(t, tb, true, true, tomb.Stop) | 15 | testState(t, tb, true, true, nil) |
164 | 17 | } | 16 | } |
165 | 18 | 17 | ||
175 | 19 | func TestFatal(t *testing.T) { | 18 | func TestKill(t *testing.T) { |
176 | 20 | tb := tomb.New() | 19 | // a nil reason flags the goroutine as dying |
177 | 21 | 20 | tb := new(tomb.Tomb) | |
178 | 22 | // the Stop reason flags the goroutine as dying | 21 | tb.Kill(nil) |
179 | 23 | tb = tomb.New() | 22 | testState(t, tb, true, false, nil) |
180 | 24 | tb.Fatal(tomb.Stop) | 23 | |
181 | 25 | testState(t, tb, true, false, tomb.Stop) | 24 | // a non-nil reason now will override Kill |
173 | 26 | |||
174 | 27 | // a non-Stop reason now will override Stop | ||
182 | 28 | err := errors.New("some error") | 25 | err := errors.New("some error") |
184 | 29 | tb.Fatal(err) | 26 | tb.Kill(err) |
185 | 30 | testState(t, tb, true, false, err) | 27 | testState(t, tb, true, false, err) |
186 | 31 | 28 | ||
187 | 32 | // another non-nil reason won't replace the first one | 29 | // another non-nil reason won't replace the first one |
189 | 33 | tb.Fatal(errors.New("ignore me")) | 30 | tb.Kill(errors.New("ignore me")) |
190 | 34 | testState(t, tb, true, false, err) | 31 | testState(t, tb, true, false, err) |
191 | 35 | 32 | ||
192 | 36 | tb.Done() | 33 | tb.Done() |
193 | 37 | testState(t, tb, true, true, err) | 34 | testState(t, tb, true, true, err) |
194 | 38 | } | 35 | } |
195 | 39 | 36 | ||
198 | 40 | func TestFatalf(t *testing.T) { | 37 | func TestKillf(t *testing.T) { |
199 | 41 | tb := tomb.New() | 38 | tb := new(tomb.Tomb) |
200 | 42 | 39 | ||
201 | 43 | err := errors.New("BOOM") | 40 | err := errors.New("BOOM") |
203 | 44 | tb.Fatalf("BO%s", "OM") | 41 | tb.Killf("BO%s", "OM") |
204 | 45 | testState(t, tb, true, false, err) | 42 | testState(t, tb, true, false, err) |
205 | 46 | 43 | ||
208 | 47 | // another non-Stop reason won't replace the first one | 44 | // another non-nil reason won't replace the first one |
209 | 48 | tb.Fatalf("ignore me") | 45 | tb.Killf("ignore me") |
210 | 49 | testState(t, tb, true, false, err) | 46 | testState(t, tb, true, false, err) |
211 | 50 | 47 | ||
212 | 51 | tb.Done() | 48 | tb.Done() |
213 | @@ -54,7 +51,7 @@ | |||
214 | 54 | 51 | ||
215 | 55 | func testState(t *testing.T, tb *tomb.Tomb, wantDying, wantDead bool, wantErr error) { | 52 | func testState(t *testing.T, tb *tomb.Tomb, wantDying, wantDead bool, wantErr error) { |
216 | 56 | select { | 53 | select { |
218 | 57 | case <-tb.Dying: | 54 | case <-tb.Dying(): |
219 | 58 | if !wantDying { | 55 | if !wantDying { |
220 | 59 | t.Error("<-Dying: should block") | 56 | t.Error("<-Dying: should block") |
221 | 60 | } | 57 | } |
222 | @@ -65,7 +62,7 @@ | |||
223 | 65 | } | 62 | } |
224 | 66 | seemsDead := false | 63 | seemsDead := false |
225 | 67 | select { | 64 | select { |
227 | 68 | case <-tb.Dead: | 65 | case <-tb.Dead(): |
228 | 69 | if !wantDead { | 66 | if !wantDead { |
229 | 70 | t.Error("<-Dead: should block") | 67 | t.Error("<-Dead: should block") |
230 | 71 | } | 68 | } |
231 | @@ -80,11 +77,10 @@ | |||
232 | 80 | } | 77 | } |
233 | 81 | if wantDead && seemsDead { | 78 | if wantDead && seemsDead { |
234 | 82 | waitErr := tb.Wait() | 79 | waitErr := tb.Wait() |
240 | 83 | if wantErr == tomb.Stop { | 80 | switch { |
241 | 84 | if waitErr != nil { | 81 | case waitErr == tomb.ErrStillRunning: |
242 | 85 | t.Errorf("Wait: want nil, got %#v", waitErr) | 82 | t.Errorf("Wait should not return ErrStillRunning") |
243 | 86 | } | 83 | case !reflect.DeepEqual(waitErr, wantErr): |
239 | 87 | } else if !reflect.DeepEqual(waitErr, wantErr) { | ||
244 | 88 | t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr) | 84 | t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr) |
245 | 89 | } | 85 | } |
246 | 90 | } | 86 | } |
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