Merge pull request #8571 from ncdc/3631-stdout-premature-eof
Fix stdout premature EOF
This commit is contained in:
commit
f936a10d80
|
@ -83,7 +83,6 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
var (
|
var (
|
||||||
cStdin io.ReadCloser
|
cStdin io.ReadCloser
|
||||||
cStdout, cStderr io.Writer
|
cStdout, cStderr io.Writer
|
||||||
cStdinCloser io.Closer
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if stdin {
|
if stdin {
|
||||||
|
@ -94,7 +93,6 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
io.Copy(w, job.Stdin)
|
io.Copy(w, job.Stdin)
|
||||||
}()
|
}()
|
||||||
cStdin = r
|
cStdin = r
|
||||||
cStdinCloser = job.Stdin
|
|
||||||
}
|
}
|
||||||
if stdout {
|
if stdout {
|
||||||
cStdout = job.Stdout
|
cStdout = job.Stdout
|
||||||
|
@ -103,7 +101,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
cStderr = job.Stderr
|
cStderr = job.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
<-daemon.attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
<-daemon.attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdout, cStderr)
|
||||||
// If we are in stdinonce mode, wait for the process to end
|
// If we are in stdinonce mode, wait for the process to end
|
||||||
// otherwise, simply return
|
// otherwise, simply return
|
||||||
if container.Config.StdinOnce && !container.Config.Tty {
|
if container.Config.StdinOnce && !container.Config.Tty {
|
||||||
|
@ -113,7 +111,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
return engine.StatusOK
|
return engine.StatusOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
||||||
var (
|
var (
|
||||||
cStdout, cStderr io.ReadCloser
|
cStdout, cStderr io.ReadCloser
|
||||||
nJobs int
|
nJobs int
|
||||||
|
@ -130,10 +128,10 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
go func() {
|
go func() {
|
||||||
log.Debugf("attach: stdin: begin")
|
log.Debugf("attach: stdin: begin")
|
||||||
defer log.Debugf("attach: stdin: end")
|
defer log.Debugf("attach: stdin: end")
|
||||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
||||||
if stdinOnce && !tty {
|
if stdinOnce && !tty {
|
||||||
defer cStdin.Close()
|
defer cStdin.Close()
|
||||||
} else {
|
} else {
|
||||||
|
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||||
defer func() {
|
defer func() {
|
||||||
if cStdout != nil {
|
if cStdout != nil {
|
||||||
cStdout.Close()
|
cStdout.Close()
|
||||||
|
@ -173,9 +171,6 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
if stdinOnce && stdin != nil {
|
if stdinOnce && stdin != nil {
|
||||||
defer stdin.Close()
|
defer stdin.Close()
|
||||||
}
|
}
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
_, err := io.Copy(stdout, cStdout)
|
_, err := io.Copy(stdout, cStdout)
|
||||||
if err == io.ErrClosedPipe {
|
if err == io.ErrClosedPipe {
|
||||||
err = nil
|
err = nil
|
||||||
|
@ -189,9 +184,6 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
} else {
|
} else {
|
||||||
// Point stdout of container to a no-op writer.
|
// Point stdout of container to a no-op writer.
|
||||||
go func() {
|
go func() {
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
|
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
|
||||||
log.Errorf("attach: stdout pipe: %s", err)
|
log.Errorf("attach: stdout pipe: %s", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -213,9 +205,6 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
if stdinOnce && stdin != nil {
|
if stdinOnce && stdin != nil {
|
||||||
defer stdin.Close()
|
defer stdin.Close()
|
||||||
}
|
}
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
_, err := io.Copy(stderr, cStderr)
|
_, err := io.Copy(stderr, cStderr)
|
||||||
if err == io.ErrClosedPipe {
|
if err == io.ErrClosedPipe {
|
||||||
err = nil
|
err = nil
|
||||||
|
@ -229,10 +218,6 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
} else {
|
} else {
|
||||||
// Point stderr at a no-op writer.
|
// Point stderr at a no-op writer.
|
||||||
go func() {
|
go func() {
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if cStderr, err := streamConfig.StderrPipe(); err != nil {
|
if cStderr, err := streamConfig.StderrPipe(); err != nil {
|
||||||
log.Errorf("attach: stdout pipe: %s", err)
|
log.Errorf("attach: stdout pipe: %s", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -251,8 +236,6 @@ func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// FIXME: how to clean up the stdin goroutine without the unwanted side effect
|
|
||||||
// of closing the passed stdin? Add an intermediary io.Pipe?
|
|
||||||
for i := 0; i < nJobs; i++ {
|
for i := 0; i < nJobs; i++ {
|
||||||
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
||||||
if err := <-errors; err != nil {
|
if err := <-errors; err != nil {
|
||||||
|
|
|
@ -155,7 +155,6 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||||
var (
|
var (
|
||||||
cStdin io.ReadCloser
|
cStdin io.ReadCloser
|
||||||
cStdout, cStderr io.Writer
|
cStdout, cStderr io.Writer
|
||||||
cStdinCloser io.Closer
|
|
||||||
execName = job.Args[0]
|
execName = job.Args[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -183,10 +182,10 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
defer log.Debugf("Closing buffered stdin pipe")
|
||||||
io.Copy(w, job.Stdin)
|
io.Copy(w, job.Stdin)
|
||||||
}()
|
}()
|
||||||
cStdin = r
|
cStdin = r
|
||||||
cStdinCloser = job.Stdin
|
|
||||||
}
|
}
|
||||||
if execConfig.OpenStdout {
|
if execConfig.OpenStdout {
|
||||||
cStdout = job.Stdout
|
cStdout = job.Stdout
|
||||||
|
@ -204,7 +203,7 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||||
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||||
}
|
}
|
||||||
|
|
||||||
attachErr := d.attach(&execConfig.StreamConfig, execConfig.OpenStdin, false, execConfig.ProcessConfig.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
attachErr := d.attach(&execConfig.StreamConfig, execConfig.OpenStdin, false, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr)
|
||||||
|
|
||||||
execErr := make(chan error)
|
execErr := make(chan error)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
@ -2446,3 +2447,46 @@ func TestRunVolumesCleanPaths(t *testing.T) {
|
||||||
|
|
||||||
logDone("run - volume paths are cleaned")
|
logDone("run - volume paths are cleaned")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for #3631
|
||||||
|
func TestRunSlowStdoutConsumer(t *testing.T) {
|
||||||
|
defer deleteAllContainers()
|
||||||
|
|
||||||
|
c := exec.Command("/bin/bash", "-c", dockerBinary+` run --rm -i busybox /bin/sh -c "dd if=/dev/zero of=/foo bs=1024 count=2000 &>/dev/null; catv /foo"`)
|
||||||
|
|
||||||
|
stdout, err := c.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n, err := consumeSlow(stdout, 10000, 5*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := 2 * 1024 * 2000
|
||||||
|
if n != expected {
|
||||||
|
t.Fatalf("Expected %d, got %d", expected, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("run - slow consumer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeSlow(reader io.Reader, chunkSize int, interval time.Duration) (n int, err error) {
|
||||||
|
buffer := make([]byte, chunkSize)
|
||||||
|
for {
|
||||||
|
var readBytes int
|
||||||
|
readBytes, err = reader.Read(buffer)
|
||||||
|
n += readBytes
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue