... | ... |
@@ -55,17 +55,21 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { |
55 | 55 |
return nil, err |
56 | 56 |
} |
57 | 57 |
pipeR, pipeW := io.Pipe() |
58 |
+ errChan := make(chan []byte) |
|
58 | 59 |
go func() { |
59 |
- _, err := io.Copy(pipeW, stdout) |
|
60 |
- if err != nil { |
|
61 |
- pipeW.CloseWithError(err) |
|
62 |
- } |
|
63 | 60 |
errText, e := ioutil.ReadAll(stderr) |
64 | 61 |
if e != nil { |
65 | 62 |
errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")") |
66 | 63 |
} |
64 |
+ errChan <- errText |
|
65 |
+ }() |
|
66 |
+ go func() { |
|
67 |
+ _, err := io.Copy(pipeW, stdout) |
|
68 |
+ if err != nil { |
|
69 |
+ pipeW.CloseWithError(err) |
|
70 |
+ } |
|
71 |
+ errText := <-errChan |
|
67 | 72 |
if err := cmd.Wait(); err != nil { |
68 |
- // FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer? |
|
69 | 73 |
pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText))) |
70 | 74 |
} else { |
71 | 75 |
pipeW.Close() |
... | ... |
@@ -1,12 +1,26 @@ |
1 | 1 |
package docker |
2 | 2 |
|
3 | 3 |
import ( |
4 |
+ "io" |
|
4 | 5 |
"io/ioutil" |
5 | 6 |
"os" |
6 | 7 |
"os/exec" |
7 | 8 |
"testing" |
8 | 9 |
) |
9 | 10 |
|
11 |
+func TestCmdStreamLargeStderr(t *testing.T) { |
|
12 |
+ // This test checks for deadlock; thus, the main failure mode of this test is deadlocking. |
|
13 |
+ cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") |
|
14 |
+ out, err := CmdStream(cmd) |
|
15 |
+ if err != nil { |
|
16 |
+ t.Fatalf("Failed to start command: " + err.Error()) |
|
17 |
+ } |
|
18 |
+ _, err = io.Copy(ioutil.Discard, out) |
|
19 |
+ if err != nil { |
|
20 |
+ t.Fatalf("Command should not have failed (err=%s...)", err.Error()[:100]) |
|
21 |
+ } |
|
22 |
+} |
|
23 |
+ |
|
10 | 24 |
func TestCmdStreamBad(t *testing.T) { |
11 | 25 |
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") |
12 | 26 |
out, err := CmdStream(badCmd) |