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

Commit ccefd23

Browse files
authored
Merge pull request #779 from ibrasho-forks/resolve-possible-race-in-activityBuffer
internal/gps: Resolve possible race in gps.activityBuffer
2 parents 3781a6f + 24a17e0 commit ccefd23

File tree

2 files changed

+67
-39
lines changed

2 files changed

+67
-39
lines changed

internal/gps/cmd.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,11 @@ func (c *monitoredCmd) hasTimedOut() bool {
127127

128128
func (c *monitoredCmd) combinedOutput(ctx context.Context) ([]byte, error) {
129129
if err := c.run(ctx); err != nil {
130-
return c.stderr.buf.Bytes(), err
130+
return c.stderr.Bytes(), err
131131
}
132132

133133
// FIXME(sdboyer) this is not actually combined output
134-
return c.stdout.buf.Bytes(), nil
134+
return c.stdout.Bytes(), nil
135135
}
136136

137137
// activityBuffer is a buffer that keeps track of the last time a Write
@@ -150,14 +150,31 @@ func newActivityBuffer() *activityBuffer {
150150

151151
func (b *activityBuffer) Write(p []byte) (int, error) {
152152
b.Lock()
153-
b.lastActivityStamp = time.Now()
154153
defer b.Unlock()
154+
155+
b.lastActivityStamp = time.Now()
156+
155157
return b.buf.Write(p)
156158
}
157159

160+
func (b *activityBuffer) String() string {
161+
b.Lock()
162+
defer b.Unlock()
163+
164+
return b.buf.String()
165+
}
166+
167+
func (b *activityBuffer) Bytes() []byte {
168+
b.Lock()
169+
defer b.Unlock()
170+
171+
return b.buf.Bytes()
172+
}
173+
158174
func (b *activityBuffer) lastActivity() time.Time {
159175
b.Lock()
160176
defer b.Unlock()
177+
161178
return b.lastActivityStamp
162179
}
163180

internal/gps/cmd_test.go

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,49 +32,60 @@ func TestMonitoredCmd(t *testing.T) {
3232
}
3333
defer os.Remove("./echosleep")
3434

35-
cmd := mkTestCmd(2)
36-
err = cmd.run(context.Background())
37-
if err != nil {
38-
t.Errorf("Expected command not to fail: %s", err)
35+
tests := []struct {
36+
name string
37+
iterations int
38+
output string
39+
err bool
40+
timeout bool
41+
}{
42+
{"success", 2, "foo\nfoo\n", false, false},
43+
{"timeout", 5, "foo\nfoo\nfoo\nfoo\n", true, true},
3944
}
4045

41-
expectedOutput := "foo\nfoo\n"
42-
if cmd.stdout.buf.String() != expectedOutput {
43-
t.Errorf("Unexpected output:\n\t(GOT): %s\n\t(WNT): %s", cmd.stdout.buf.String(), expectedOutput)
44-
}
46+
for _, want := range tests {
47+
t.Run(want.name, func(t *testing.T) {
48+
cmd := mkTestCmd(want.iterations)
4549

46-
cmd2 := mkTestCmd(10)
47-
err = cmd2.run(context.Background())
48-
if err == nil {
49-
t.Error("Expected command to fail")
50-
}
50+
err := cmd.run(context.Background())
51+
if !want.err && err != nil {
52+
t.Errorf("Eexpected command not to fail, got error: %s", err)
53+
} else if want.err && err == nil {
54+
t.Error("expected command to fail")
55+
}
5156

52-
_, ok := err.(*noProgressError)
53-
if !ok {
54-
t.Errorf("Expected a timeout error, but got: %s", err)
55-
}
57+
got := cmd.stdout.String()
58+
if want.output != got {
59+
t.Errorf("unexpected output:\n\t(GOT):\n%s\n\t(WNT):\n%s", got, want.output)
60+
}
5661

57-
expectedOutput = "foo\nfoo\nfoo\nfoo\n"
58-
if cmd2.stdout.buf.String() != expectedOutput {
59-
t.Errorf("Unexpected output:\n\t(GOT): %s\n\t(WNT): %s", cmd2.stdout.buf.String(), expectedOutput)
62+
if want.timeout {
63+
_, ok := err.(*noProgressError)
64+
if !ok {
65+
t.Errorf("Expected a timeout error, but got: %s", err)
66+
}
67+
}
68+
})
6069
}
6170

62-
ctx, cancel := context.WithCancel(context.Background())
63-
sync1, errchan := make(chan struct{}), make(chan error)
64-
cmd3 := mkTestCmd(2)
65-
go func() {
66-
close(sync1)
67-
errchan <- cmd3.run(ctx)
68-
}()
71+
t.Run("cancel", func(t *testing.T) {
72+
ctx, cancel := context.WithCancel(context.Background())
73+
sync, errchan := make(chan struct{}), make(chan error)
74+
cmd := mkTestCmd(2)
75+
go func() {
76+
close(sync)
77+
errchan <- cmd.run(ctx)
78+
}()
6979

70-
// Make sure goroutine is at least started before we cancel the context.
71-
<-sync1
72-
// Give it a bit to get the process started.
73-
<-time.After(5 * time.Millisecond)
74-
cancel()
80+
// Make sure goroutine is at least started before we cancel the context.
81+
<-sync
82+
// Give it a bit to get the process started.
83+
<-time.After(5 * time.Millisecond)
84+
cancel()
7585

76-
err = <-errchan
77-
if err != context.Canceled {
78-
t.Errorf("should have gotten canceled error, got %s", err)
79-
}
86+
err := <-errchan
87+
if err != context.Canceled {
88+
t.Errorf("expected a canceled error, got %s", err)
89+
}
90+
})
8091
}

0 commit comments

Comments
 (0)