Skip to content

Commit a5c2862

Browse files
Merge pull request containerd#58 from sipsma/gorace
Fix data race in use of cmd output buffers.
2 parents a2952bc + 469fa2c commit a5c2862

File tree

3 files changed

+55
-15
lines changed

3 files changed

+55
-15
lines changed

runc.go

+24-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package runc
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"encoding/json"
2223
"errors"
@@ -72,11 +73,12 @@ type Runc struct {
7273
// List returns all containers created inside the provided runc root directory
7374
func (r *Runc) List(context context.Context) ([]*Container, error) {
7475
data, err := cmdOutput(r.command(context, "list", "--format=json"), false)
76+
defer putBuf(data)
7577
if err != nil {
7678
return nil, err
7779
}
7880
var out []*Container
79-
if err := json.Unmarshal(data, &out); err != nil {
81+
if err := json.Unmarshal(data.Bytes(), &out); err != nil {
8082
return nil, err
8183
}
8284
return out, nil
@@ -85,11 +87,12 @@ func (r *Runc) List(context context.Context) ([]*Container, error) {
8587
// State returns the state for the container provided by id
8688
func (r *Runc) State(context context.Context, id string) (*Container, error) {
8789
data, err := cmdOutput(r.command(context, "state", id), true)
90+
defer putBuf(data)
8891
if err != nil {
89-
return nil, fmt.Errorf("%s: %s", err, data)
92+
return nil, fmt.Errorf("%s: %s", err, data.String())
9093
}
9194
var c Container
92-
if err := json.Unmarshal(data, &c); err != nil {
95+
if err := json.Unmarshal(data.Bytes(), &c); err != nil {
9396
return nil, err
9497
}
9598
return &c, nil
@@ -154,8 +157,9 @@ func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOp
154157

155158
if cmd.Stdout == nil && cmd.Stderr == nil {
156159
data, err := cmdOutput(cmd, true)
160+
defer putBuf(data)
157161
if err != nil {
158-
return fmt.Errorf("%s: %s", err, data)
162+
return fmt.Errorf("%s: %s", err, data.String())
159163
}
160164
return nil
161165
}
@@ -233,8 +237,9 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts
233237
}
234238
if cmd.Stdout == nil && cmd.Stderr == nil {
235239
data, err := cmdOutput(cmd, true)
240+
defer putBuf(data)
236241
if err != nil {
237-
return fmt.Errorf("%s: %s", err, data)
242+
return fmt.Errorf("%s: %s", err, data.String())
238243
}
239244
return nil
240245
}
@@ -399,11 +404,12 @@ func (r *Runc) Resume(context context.Context, id string) error {
399404
// Ps lists all the processes inside the container returning their pids
400405
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
401406
data, err := cmdOutput(r.command(context, "ps", "--format", "json", id), true)
407+
defer putBuf(data)
402408
if err != nil {
403-
return nil, fmt.Errorf("%s: %s", err, data)
409+
return nil, fmt.Errorf("%s: %s", err, data.String())
404410
}
405411
var pids []int
406-
if err := json.Unmarshal(data, &pids); err != nil {
412+
if err := json.Unmarshal(data.Bytes(), &pids); err != nil {
407413
return nil, err
408414
}
409415
return pids, nil
@@ -412,11 +418,12 @@ func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
412418
// Top lists all the processes inside the container returning the full ps data
413419
func (r *Runc) Top(context context.Context, id string, psOptions string) (*TopResults, error) {
414420
data, err := cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true)
421+
defer putBuf(data)
415422
if err != nil {
416-
return nil, fmt.Errorf("%s: %s", err, data)
423+
return nil, fmt.Errorf("%s: %s", err, data.String())
417424
}
418425

419-
topResults, err := ParsePSOutput(data)
426+
topResults, err := ParsePSOutput(data.Bytes())
420427
if err != nil {
421428
return nil, fmt.Errorf("%s: ", err)
422429
}
@@ -606,10 +613,11 @@ type Version struct {
606613
// Version returns the runc and runtime-spec versions
607614
func (r *Runc) Version(context context.Context) (Version, error) {
608615
data, err := cmdOutput(r.command(context, "--version"), false)
616+
defer putBuf(data)
609617
if err != nil {
610618
return Version{}, err
611619
}
612-
return parseVersion(data)
620+
return parseVersion(data.Bytes())
613621
}
614622

615623
func parseVersion(data []byte) (Version, error) {
@@ -687,15 +695,17 @@ func (r *Runc) runOrError(cmd *exec.Cmd) error {
687695
return err
688696
}
689697
data, err := cmdOutput(cmd, true)
698+
defer putBuf(data)
690699
if err != nil {
691-
return fmt.Errorf("%s: %s", err, data)
700+
return fmt.Errorf("%s: %s", err, data.String())
692701
}
693702
return nil
694703
}
695704

696-
func cmdOutput(cmd *exec.Cmd, combined bool) ([]byte, error) {
705+
// callers of cmdOutput are expected to call putBuf on the returned Buffer
706+
// to ensure it is released back to the shared pool after use.
707+
func cmdOutput(cmd *exec.Cmd, combined bool) (*bytes.Buffer, error) {
697708
b := getBuf()
698-
defer putBuf(b)
699709

700710
cmd.Stdout = b
701711
if combined {
@@ -711,5 +721,5 @@ func cmdOutput(cmd *exec.Cmd, combined bool) ([]byte, error) {
711721
err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
712722
}
713723

714-
return b.Bytes(), err
724+
return b, err
715725
}

runc_test.go

+27-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package runc
1818

19-
import "testing"
19+
import (
20+
"context"
21+
"sync"
22+
"testing"
23+
)
2024

2125
func TestParseVersion(t *testing.T) {
2226
const data = `runc version 1.0.0-rc3
@@ -37,3 +41,25 @@ spec: 1.0.0-rc5-dev`
3741
t.Errorf("expected spec version 1.0.0-rc5-dev but received %s", v.Spec)
3842
}
3943
}
44+
45+
func TestParallelCmds(t *testing.T) {
46+
rc := &Runc{
47+
// we don't need a real runc, we just want to test running a caller of cmdOutput in parallel
48+
Command: "/bin/true",
49+
}
50+
var wg sync.WaitGroup
51+
52+
ctx, cancel := context.WithCancel(context.Background())
53+
defer cancel()
54+
55+
for i := 0; i < 256; i++ {
56+
wg.Add(1)
57+
go func() {
58+
defer wg.Done()
59+
// We just want to fail if there is a race condition detected by
60+
// "-race", so we ignore the (expected) error here.
61+
_, _ = rc.Version(ctx)
62+
}()
63+
}
64+
wg.Wait()
65+
}

utils.go

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ func getBuf() *bytes.Buffer {
5757
}
5858

5959
func putBuf(b *bytes.Buffer) {
60+
if b == nil {
61+
return
62+
}
63+
6064
b.Reset()
6165
bytesBufferPool.Put(b)
6266
}

0 commit comments

Comments
 (0)