Skip to content

Commit cfaaeb0

Browse files
authored
Merge pull request #5957 from vvoland/stdout-trunc
container/run: Fix stdout/err truncation after container exit
2 parents ff5fdfa + 5a8120c commit cfaaeb0

File tree

3 files changed

+69
-4
lines changed

3 files changed

+69
-4
lines changed

cli/command/container/run.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,16 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
238238
return cli.StatusError{StatusCode: status}
239239
}
240240
case status := <-statusChan:
241-
// notify hijackedIOStreamer that we're exiting and wait
242-
// so that the terminal can be restored.
243-
cancelFun()
244-
<-errCh
241+
// If container exits, output stream processing may not be finished yet,
242+
// we need to keep the streamer running until all output is read.
243+
// However, if stdout or stderr is not attached, we can just exit.
244+
if !config.AttachStdout && !config.AttachStderr {
245+
// Notify hijackedIOStreamer that we're exiting and wait
246+
// so that the terminal can be restored.
247+
cancelFun()
248+
}
249+
<-errCh // Drain channel but don't care about result
250+
245251
if status != 0 {
246252
return cli.StatusError{StatusCode: status}
247253
}

cli/command/container/run_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ func TestRunAttachTermination(t *testing.T) {
147147
_ = p.Close()
148148
}()
149149

150+
var conn net.Conn
150151
killCh := make(chan struct{})
151152
attachCh := make(chan struct{})
152153
fakeCLI := test.NewFakeCli(&fakeClient{
@@ -163,6 +164,7 @@ func TestRunAttachTermination(t *testing.T) {
163164
},
164165
containerAttachFunc: func(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
165166
server, client := net.Pipe()
167+
conn = server
166168
t.Cleanup(func() {
167169
_ = server.Close()
168170
})
@@ -202,6 +204,7 @@ func TestRunAttachTermination(t *testing.T) {
202204
}
203205

204206
assert.NilError(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM))
207+
conn.Close()
205208

206209
select {
207210
case <-killCh:

e2e/container/run_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package container
33
import (
44
"bytes"
55
"fmt"
6+
"io"
7+
"math/rand"
68
"os/exec"
79
"strings"
810
"syscall"
@@ -280,3 +282,57 @@ func TestProcessTermination(t *testing.T) {
280282
ExitCode: 0,
281283
})
282284
}
285+
286+
// Adapted from https://github.com/docker/for-mac/issues/7632#issue-2932169772
287+
// Thanks [@almet](https://github.com/almet)!
288+
func TestRunReadAfterContainerExit(t *testing.T) {
289+
skip.If(t, environment.RemoteDaemon())
290+
291+
r := rand.New(rand.NewSource(0x123456))
292+
293+
const size = 18933764
294+
cmd := exec.Command("docker", "run",
295+
"--rm", "-i",
296+
"alpine",
297+
"sh", "-c", "cat -",
298+
)
299+
300+
cmd.Stdin = io.LimitReader(r, size)
301+
302+
var stderr bytes.Buffer
303+
cmd.Stderr = &stderr
304+
305+
stdout, err := cmd.StdoutPipe()
306+
assert.NilError(t, err)
307+
308+
err = cmd.Start()
309+
assert.NilError(t, err)
310+
311+
buffer := make([]byte, 1000)
312+
counter := 0
313+
totalRead := 0
314+
315+
for {
316+
n, err := stdout.Read(buffer)
317+
if n > 0 {
318+
totalRead += n
319+
}
320+
321+
// Wait 0.1s every megabyte (approx.)
322+
if counter%1000 == 0 {
323+
time.Sleep(100 * time.Millisecond)
324+
}
325+
326+
if err != nil || n == 0 {
327+
break
328+
}
329+
330+
counter++
331+
}
332+
333+
err = cmd.Wait()
334+
t.Logf("Error: %v", err)
335+
t.Logf("Stderr: %s", stderr.String())
336+
assert.Check(t, err == nil)
337+
assert.Check(t, is.Equal(totalRead, size))
338+
}

0 commit comments

Comments
 (0)