Skip to content

Commit f45eb9f

Browse files
jmartin82bradfitz
authored andcommitted
io: add error check on pipe close functions to avoid error overwriting
The current implementation allows multiple calls `Close` and `CloseWithError` in every side of the pipe, as a result, the original error can be overwritten. This CL fixes this behavior adding an error existence check on `atomicError` type and keeping the first error still available. Fixes golang#24283 Change-Id: Iefe8f758aeb775309424365f8177511062514150 GitHub-Last-Rev: b559540 GitHub-Pull-Request: golang#33239 Reviewed-on: https://go-review.googlesource.com/c/go/+/187197 Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com> Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
1 parent 89865f8 commit f45eb9f

File tree

2 files changed

+27
-16
lines changed

2 files changed

+27
-16
lines changed

src/io/pipe.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,26 @@ package io
1010
import (
1111
"errors"
1212
"sync"
13-
"sync/atomic"
1413
)
1514

16-
// atomicError is a type-safe atomic value for errors.
17-
// We use a struct{ error } to ensure consistent use of a concrete type.
18-
type atomicError struct{ v atomic.Value }
15+
// onceError is an object that will only store an error once.
16+
type onceError struct {
17+
sync.Mutex // guards following
18+
err error
19+
}
1920

20-
func (a *atomicError) Store(err error) {
21-
a.v.Store(struct{ error }{err})
21+
func (a *onceError) Store(err error) {
22+
a.Lock()
23+
defer a.Unlock()
24+
if a.err != nil {
25+
return
26+
}
27+
a.err = err
2228
}
23-
func (a *atomicError) Load() error {
24-
err, _ := a.v.Load().(struct{ error })
25-
return err.error
29+
func (a *onceError) Load() error {
30+
a.Lock()
31+
defer a.Unlock()
32+
return a.err
2633
}
2734

2835
// ErrClosedPipe is the error used for read or write operations on a closed pipe.
@@ -36,8 +43,8 @@ type pipe struct {
3643

3744
once sync.Once // Protects closing done
3845
done chan struct{}
39-
rerr atomicError
40-
werr atomicError
46+
rerr onceError
47+
werr onceError
4148
}
4249

4350
func (p *pipe) Read(b []byte) (n int, err error) {
@@ -135,6 +142,9 @@ func (r *PipeReader) Close() error {
135142

136143
// CloseWithError closes the reader; subsequent writes
137144
// to the write half of the pipe will return the error err.
145+
//
146+
// CloseWithError never overwrites the previous error if it exists
147+
// and always returns nil.
138148
func (r *PipeReader) CloseWithError(err error) error {
139149
return r.p.CloseRead(err)
140150
}
@@ -163,7 +173,8 @@ func (w *PipeWriter) Close() error {
163173
// read half of the pipe will return no bytes and the error err,
164174
// or EOF if err is nil.
165175
//
166-
// CloseWithError always returns nil.
176+
// CloseWithError never overwrites the previous error if it exists
177+
// and always returns nil.
167178
func (w *PipeWriter) CloseWithError(err error) error {
168179
return w.p.CloseWrite(err)
169180
}

src/io/pipe_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ func TestPipeCloseError(t *testing.T) {
326326
t.Errorf("Write error: got %T, want testError1", err)
327327
}
328328
r.CloseWithError(testError2{})
329-
if _, err := w.Write(nil); err != (testError2{}) {
330-
t.Errorf("Write error: got %T, want testError2", err)
329+
if _, err := w.Write(nil); err != (testError1{}) {
330+
t.Errorf("Write error: got %T, want testError1", err)
331331
}
332332

333333
r, w = Pipe()
@@ -336,8 +336,8 @@ func TestPipeCloseError(t *testing.T) {
336336
t.Errorf("Read error: got %T, want testError1", err)
337337
}
338338
w.CloseWithError(testError2{})
339-
if _, err := r.Read(nil); err != (testError2{}) {
340-
t.Errorf("Read error: got %T, want testError2", err)
339+
if _, err := r.Read(nil); err != (testError1{}) {
340+
t.Errorf("Read error: got %T, want testError1", err)
341341
}
342342
}
343343

0 commit comments

Comments
 (0)