Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 6b69a16

Browse files
authored
Merge pull request #464 from smola/race-463
transport/file: avoid race with Command.Wait, fixes #463
2 parents 83c0e73 + 6506d37 commit 6b69a16

File tree

2 files changed

+31
-21
lines changed

2 files changed

+31
-21
lines changed

plumbing/transport/file/client.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,22 @@ func (r *runner) Command(cmd string, ep transport.Endpoint, auth transport.AuthM
4646
}
4747

4848
type command struct {
49-
cmd *exec.Cmd
50-
closed bool
49+
cmd *exec.Cmd
50+
stderrCloser io.Closer
51+
closed bool
5152
}
5253

5354
func (c *command) Start() error {
5455
return c.cmd.Start()
5556
}
5657

5758
func (c *command) StderrPipe() (io.Reader, error) {
58-
return c.cmd.StderrPipe()
59+
// Pipe returned by Command.StderrPipe has a race with Read + Command.Wait.
60+
// We use an io.Pipe and close it after the command finishes.
61+
r, w := io.Pipe()
62+
c.cmd.Stderr = w
63+
c.stderrCloser = r
64+
return r, nil
5965
}
6066

6167
func (c *command) StdinPipe() (io.WriteCloser, error) {
@@ -72,7 +78,11 @@ func (c *command) Close() error {
7278
return nil
7379
}
7480

75-
defer func() { c.closed = true }()
81+
defer func() {
82+
c.closed = true
83+
_ = c.stderrCloser.Close()
84+
}()
85+
7686
err := c.cmd.Wait()
7787
if _, ok := err.(*os.PathError); ok {
7888
return nil

plumbing/transport/internal/common/common.go

+17-17
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"errors"
1111
"fmt"
1212
"io"
13+
stdioutil "io/ioutil"
1314
"strings"
1415
"time"
1516

@@ -22,7 +23,6 @@ import (
2223

2324
const (
2425
readErrorSecondsTimeout = 10
25-
errLinesBuffer = 1000
2626
)
2727

2828
var (
@@ -96,7 +96,7 @@ type session struct {
9696
advRefs *packp.AdvRefs
9797
packRun bool
9898
finished bool
99-
errLines chan string
99+
firstErrLine chan string
100100
}
101101

102102
func (c *client) newSession(s string, ep transport.Endpoint, auth transport.AuthMethod) (*session, error) {
@@ -128,26 +128,29 @@ func (c *client) newSession(s string, ep transport.Endpoint, auth transport.Auth
128128
Stdin: stdin,
129129
Stdout: stdout,
130130
Command: cmd,
131-
errLines: c.listenErrors(stderr),
131+
firstErrLine: c.listenFirstError(stderr),
132132
isReceivePack: s == transport.ReceivePackServiceName,
133133
}, nil
134134
}
135135

136-
func (c *client) listenErrors(r io.Reader) chan string {
136+
func (c *client) listenFirstError(r io.Reader) chan string {
137137
if r == nil {
138138
return nil
139139
}
140140

141-
errLines := make(chan string, errLinesBuffer)
141+
errLine := make(chan string, 1)
142142
go func() {
143143
s := bufio.NewScanner(r)
144-
for s.Scan() {
145-
line := string(s.Bytes())
146-
errLines <- line
144+
if s.Scan() {
145+
errLine <- s.Text()
146+
} else {
147+
close(errLine)
147148
}
149+
150+
_, _ = io.Copy(stdioutil.Discard, r)
148151
}()
149152

150-
return errLines
153+
return errLine
151154
}
152155

153156
// AdvertisedReferences retrieves the advertised references from the server.
@@ -296,13 +299,10 @@ func (s *session) finish() error {
296299
return nil
297300
}
298301

299-
func (s *session) Close() error {
300-
if err := s.finish(); err != nil {
301-
_ = s.Command.Close()
302-
return err
303-
}
304-
305-
return s.Command.Close()
302+
func (s *session) Close() (err error) {
303+
defer ioutil.CheckClose(s.Command, &err)
304+
err = s.finish()
305+
return
306306
}
307307

308308
func (s *session) checkNotFoundError() error {
@@ -312,7 +312,7 @@ func (s *session) checkNotFoundError() error {
312312
select {
313313
case <-t.C:
314314
return ErrTimeoutExceeded
315-
case line, ok := <-s.errLines:
315+
case line, ok := <-s.firstErrLine:
316316
if !ok {
317317
return nil
318318
}

0 commit comments

Comments
 (0)