Skip to content

Commit a165019

Browse files
author
keks
committed
big commit
- remove HandleError() because we now have CloseWithError - remove Header interface - remove all Head() methods - fix locking in chan.go - use EmitChan in ResponseEmitters - introduce cmds.ErrClosedEmitter - fix tests (some still expected errors to be emitted/marshaled)
1 parent 497db66 commit a165019

10 files changed

+132
-210
lines changed

Diff for: chan.go

+74-78
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package cmds
22

33
import (
44
"context"
5-
"fmt"
5+
"errors"
66
"io"
77
"sync"
88

@@ -15,43 +15,54 @@ func NewChanResponsePair(req *Request) (ResponseEmitter, Response) {
1515
wait := make(chan struct{})
1616

1717
r := &chanResponse{
18-
req: req,
19-
ch: ch,
20-
wait: wait,
21-
closed: make(chan struct{}),
18+
req: req,
19+
ch: ch,
20+
wait: wait,
2221
}
2322

2423
re := (*chanResponseEmitter)(r)
2524

2625
return re, r
2726
}
2827

29-
type chanResponse struct {
30-
wl sync.Mutex // lock for writing calls, i.e. Emit et al.
31-
rl sync.Mutex // lock for reading calls, i.e. Next
28+
// chanStream is the struct of both the Response and ResponseEmitter.
29+
// The methods are defined on chanResponse and chanResponseEmitter, which are
30+
// just type definitions on chanStream.
31+
type chanStream struct {
3232
req *Request
3333

34-
// wait makes header requests block until the body is sent
35-
wait chan struct{}
36-
// waitOnce makes sure we only close wait once
37-
waitOnce sync.Once
34+
// rl is a lock for reading calls, i.e. Next.
35+
rl sync.Mutex
3836

39-
// ch is used to send values from emitter to response
37+
// ch is used to send values from emitter to response.
38+
// When Emit received a channel close, it sets it to nil.
39+
// It is protected by rl.
4040
ch chan interface{}
4141

42-
emitted bool
43-
err error
44-
length uint64
42+
// wl is a lock for writing calls, i.e. Emit, Close(WithError) and SetLength.
43+
wl sync.Mutex
44+
45+
// closed stores whether this stream is closed.
46+
// It is protected by wl.
47+
closed bool
48+
49+
// wait is closed when the stream is closed or the first value is emitted.
50+
// Error and Length both wait for wait to be closed.
51+
// It is protected by wl.
52+
wait chan struct{}
53+
54+
// err is the error that the stream was closed with.
55+
// It is written once under lock wl, but only read after wait is closed (which also happens under wl)
56+
err error
4557

46-
closeOnce sync.Once
47-
closed chan struct{}
58+
// length is the length of the response.
59+
// It can be set by calling SetLength, but only before the first call to Emit, Close or CloseWithError.
60+
length uint64
4861
}
4962

50-
func (r *chanResponse) Request() *Request {
51-
if r == nil {
52-
return nil
53-
}
63+
type chanResponse chanStream
5464

65+
func (r *chanResponse) Request() *Request {
5566
return r.req
5667
}
5768

@@ -75,25 +86,6 @@ func (r *chanResponse) Length() uint64 {
7586
return r.length
7687
}
7788

78-
func (re *chanResponse) Head() Head {
79-
<-re.wait
80-
81-
var err error
82-
if re.err != io.EOF {
83-
err = re.err
84-
}
85-
86-
cmdErr, ok := err.(*cmdkit.Error)
87-
if !ok && err != nil {
88-
cmdErr = &cmdkit.Error{Message: err.Error()}
89-
}
90-
91-
return Head{
92-
Len: re.length,
93-
Err: cmdErr,
94-
}
95-
}
96-
9789
func (r *chanResponse) Next() (interface{}, error) {
9890
if r == nil {
9991
return nil, io.EOF
@@ -111,18 +103,11 @@ func (r *chanResponse) Next() (interface{}, error) {
111103
defer r.rl.Unlock()
112104

113105
select {
114-
case <-r.closed:
115-
return nil, r.err
116106
case v, ok := <-r.ch:
117107
if !ok {
118-
r.ch = nil
119108
return nil, r.err
120109
}
121110

122-
if err, ok := v.(cmdkit.Error); ok {
123-
v = &err
124-
}
125-
126111
switch val := v.(type) {
127112
case Single:
128113
return val.Value, nil
@@ -138,69 +123,83 @@ type chanResponseEmitter chanResponse
138123

139124
func (re *chanResponseEmitter) Emit(v interface{}) error {
140125
// channel emission iteration
141-
// TODO maybe remove this and use EmitChan instead of calling Emit directly?
142126
if ch, ok := v.(chan interface{}); ok {
143127
v = (<-chan interface{})(ch)
144128
}
145129
if ch, isChan := v.(<-chan interface{}); isChan {
146130
return EmitChan(re, ch)
147131
}
148132

149-
// unblock Length(), Error() and Head()
150-
re.waitOnce.Do(func() {
151-
close(re.wait)
152-
})
153-
154133
re.wl.Lock()
155134
defer re.wl.Unlock()
156135

157136
if _, ok := v.(Single); ok {
158-
defer re.closeWithError(io.EOF)
137+
defer re.closeWithError(nil)
138+
}
159139

160140
// Initially this library allowed commands to return errors by sending an
161141
// error value along a stream. We removed that in favour of CloseWithError,
162142
// so we want to make sure we catch situations where some code still uses the
163143
// old error emitting semantics and _panic_ in those situations.
164144
debug.AssertNotError(v)
165145

146+
// unblock Length() and Error()
147+
select {
148+
case <-re.wait:
149+
default:
150+
close(re.wait)
151+
}
152+
153+
// make sure we check whether the stream is closed *before accessing re.ch*!
154+
// re.ch is set to nil, but is not protected by a shared mutex (because that
155+
// wouldn't make sense).
156+
// re.closed is set in a critical section protected by re.wl (we also took
157+
// that lock), so we can be sure that this check is not racy.
158+
if re.closed {
159+
return ErrClosedEmitter
166160
}
167161

168162
ctx := re.req.Context
169163

170164
select {
171-
case <-re.closed:
172-
return fmt.Errorf("emitter closed")
173165
case re.ch <- v:
174166
return nil
175167
case <-ctx.Done():
176168
return ctx.Err()
177169
}
178170
}
179171

172+
func (re *chanResponseEmitter) Close() error {
173+
return re.CloseWithError(nil)
174+
}
175+
180176
func (re *chanResponseEmitter) SetLength(l uint64) {
181177
re.wl.Lock()
182178
defer re.wl.Unlock()
183179

184-
// don't change value after emitting
185-
if re.emitted {
186-
return
180+
// don't change value after emitting or closing
181+
select {
182+
case <-re.wait:
183+
default:
184+
re.length = l
187185
}
188-
189-
re.length = l
190-
}
191-
192-
func (re *chanResponseEmitter) Close() error {
193-
return re.CloseWithError(nil)
194186
}
195187

196188
func (re *chanResponseEmitter) CloseWithError(err error) error {
197189
re.wl.Lock()
198190
defer re.wl.Unlock()
199191

200-
return re.closeWithError(err)
192+
if re.closed {
193+
return errors.New("close of closed emitter")
194+
}
195+
196+
re.closeWithError(err)
197+
return nil
201198
}
202199

203-
func (re *chanResponseEmitter) closeWithError(err error) error {
200+
func (re *chanResponseEmitter) closeWithError(err error) {
201+
re.closed = true
202+
204203
if err == nil {
205204
err = io.EOF
206205
}
@@ -209,16 +208,13 @@ func (re *chanResponseEmitter) closeWithError(err error) error {
209208
err = &e
210209
}
211210

212-
re.closeOnce.Do(func() {
213-
re.err = err
214-
close(re.ch)
215-
close(re.closed)
216-
})
211+
re.err = err
212+
close(re.ch)
217213

218-
// unblock Length(), Error() and Head()
219-
re.waitOnce.Do(func() {
214+
// unblock Length() and Error()
215+
select {
216+
case <-re.wait:
217+
default:
220218
close(re.wait)
221-
})
222-
223-
return nil
219+
}
224220
}

Diff for: cli/responseemitter.go

+5-28
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,11 @@ type responseEmitter struct {
5151
stderr io.Writer
5252

5353
length uint64
54-
err *cmdkit.Error // TODO do we really need this?
5554
enc cmds.Encoder
5655
encType cmds.EncodingType
5756
exit int
5857
closed bool
5958

60-
errOccurred bool
61-
6259
ch chan<- int
6360
}
6461

@@ -93,9 +90,7 @@ func (re *responseEmitter) CloseWithError(err error) error {
9390
return errors.New("closing closed emitter")
9491
}
9592

96-
re.errOccurred = true
9793
re.exit = 1 // TODO we could let err carry an exit code
98-
re.err = e
9994

10095
_, err = fmt.Fprintln(re.stderr, "Error:", e.Message)
10196
if err != nil {
@@ -164,17 +159,6 @@ func (re *responseEmitter) close() error {
164159
return nil
165160
}
166161

167-
// Head returns the current head.
168-
// TODO: maybe it makes sense to make these pointers to shared memory?
169-
// might not be so clever though...concurrency and stuff
170-
// TODO: can we maybe drop this function? Then we could also remove the err struct field
171-
func (re *responseEmitter) Head() cmds.Head {
172-
return cmds.Head{
173-
Len: re.length,
174-
Err: re.err,
175-
}
176-
}
177-
178162
func (re *responseEmitter) Emit(v interface{}) error {
179163
// unwrap
180164
if val, ok := v.(cmds.Single); ok {
@@ -187,9 +171,13 @@ func (re *responseEmitter) Emit(v interface{}) error {
187171
// old error emitting semantics and _panic_ in those situations.
188172
debug.AssertNotError(v)
189173

174+
// channel emission iteration
190175
if ch, ok := v.(chan interface{}); ok {
191176
v = (<-chan interface{})(ch)
192177
}
178+
if ch, isChan := v.(<-chan interface{}); isChan {
179+
return cmds.EmitChan(re, ch)
180+
}
193181

194182
// TODO find a better solution for this.
195183
// Idea: use the actual cmd.Type and not *cmd.Type
@@ -201,19 +189,8 @@ func (re *responseEmitter) Emit(v interface{}) error {
201189
v = *c
202190
}
203191

204-
if ch, isChan := v.(<-chan interface{}); isChan {
205-
log.Debug("iterating over chan...", ch)
206-
for v = range ch {
207-
err := re.Emit(v)
208-
if err != nil {
209-
return err
210-
}
211-
}
212-
return nil
213-
}
214-
215192
if re.isClosed() {
216-
return io.ErrClosedPipe
193+
return cmds.ErrClosedEmitter
217194
}
218195

219196
var err error

Diff for: command_test.go

+4-10
Original file line numberDiff line numberDiff line change
@@ -185,17 +185,13 @@ func TestPostRun(t *testing.T) {
185185
finalLength: 4,
186186
next: []interface{}{14},
187187
postRun: func(res Response, re ResponseEmitter) error {
188-
defer re.Close()
189188
l := res.Length()
190189
re.SetLength(l + 1)
191190

192191
for {
193192
v, err := res.Next()
194-
if err == io.EOF {
195-
return nil
196-
}
193+
t.Log("PostRun: Next returned", v, err)
197194
if err != nil {
198-
t.Error(err) // TODO keks: should this go?
199195
return err
200196
}
201197

@@ -221,10 +217,6 @@ func TestPostRun(t *testing.T) {
221217
t.Fatal(err)
222218
}
223219
}
224-
err := re.Close()
225-
if err != nil {
226-
t.Fatal(err)
227-
}
228220
return nil
229221
},
230222
PostRun: PostRunMap{
@@ -263,8 +255,9 @@ func TestPostRun(t *testing.T) {
263255

264256
go func() {
265257
err := cmd.PostRun[PostRunType(encType)](postres, postre)
258+
err = postre.CloseWithError(err)
266259
if err != nil {
267-
t.Error("error in PostRun: ", err)
260+
t.Error("error closing after PostRun: ", err)
268261
}
269262
}()
270263

@@ -280,6 +273,7 @@ func TestPostRun(t *testing.T) {
280273

281274
go func() {
282275
v, err := res.Next()
276+
t.Log("next returned", v, err)
283277
if err != nil {
284278
close(ch)
285279
t.Fatal(err)

Diff for: helpers_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ func (s *testEmitter) Close() error {
1818

1919
func (s *testEmitter) SetLength(_ uint64) {}
2020
func (s *testEmitter) CloseWithError(err error) error {
21-
(*testing.T)(s).Error(err)
21+
if err != nil {
22+
(*testing.T)(s).Error(err)
23+
}
2224
return nil
2325
}
2426
func (s *testEmitter) Emit(value interface{}) error {

0 commit comments

Comments
 (0)