From 3b41fdc9e8e69886cedc942ddebcfb12ec1848b5 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Wed, 8 Oct 2014 08:41:26 -0400 Subject: [PATCH] pass extra file to child process as status handler When stdout/stderr is closed prematurely, the proxy's writes to stdout/stderr (i.e. `log.Errorf/log.Printf`) will returns with EPIPE error, and go runtime will terminate the proxy when stdout/stderr writes trigger 10 EPIPE errors. instead of using stdout/stderr as the status handler, we pass an extra file to the child process and write `0\n` or `1\nerror message` to it and close it after. This allow the child process to handle stdout/stderr as normal. Docker-DCO-1.1-Signed-off-by: Daniel, Dao Quang Minh (github: dqminh) Upstream-commit: 3b9d88210e763bebdfd7badb6ed3fd507d0f6513 Component: engine --- .../daemon/networkdriver/portmapper/proxy.go | 25 +++++++------- .../integration-cli/docker_cli_run_test.go | 33 +++++++++++++++++++ 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/components/engine/daemon/networkdriver/portmapper/proxy.go b/components/engine/daemon/networkdriver/portmapper/proxy.go index 1e8e0c3987..341f0605e5 100644 --- a/components/engine/daemon/networkdriver/portmapper/proxy.go +++ b/components/engine/daemon/networkdriver/portmapper/proxy.go @@ -36,16 +36,18 @@ type proxyCommand struct { // execProxy is the reexec function that is registered to start the userland proxies func execProxy() { + f := os.NewFile(3, "signal-parent") host, container := parseHostContainerAddrs() p, err := proxy.NewProxy(host, container) if err != nil { - os.Stdout.WriteString("1\n") - fmt.Fprint(os.Stderr, err) + fmt.Fprintf(f, "1\n%s", err) + f.Close() os.Exit(1) } go handleStopSignals(p) - os.Stdout.WriteString("0\n") + fmt.Fprint(f, "0\n") + f.Close() // Run will block until the proxy stops p.Run() @@ -111,27 +113,24 @@ func NewProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net. } func (p *proxyCommand) Start() error { - stdout, err := p.cmd.StdoutPipe() + r, w, err := os.Pipe() if err != nil { - return err + return fmt.Errorf("proxy unable to open os.Pipe %s", err) } - defer stdout.Close() - stderr, err := p.cmd.StderrPipe() - if err != nil { - return err - } - defer stderr.Close() + defer r.Close() + p.cmd.ExtraFiles = []*os.File{w} if err := p.cmd.Start(); err != nil { return err } + w.Close() errchan := make(chan error, 1) go func() { buf := make([]byte, 2) - stdout.Read(buf) + r.Read(buf) if string(buf) != "0\n" { - errStr, _ := ioutil.ReadAll(stderr) + errStr, _ := ioutil.ReadAll(r) errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr) return } diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 2fd545af8d..aabf6e74d8 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -2093,6 +2093,39 @@ func TestRunPortInUse(t *testing.T) { logDone("run - fail if port already in use") } +// https://github.com/docker/docker/issues/8428 +func TestRunPortProxy(t *testing.T) { + defer deleteAllContainers() + + port := "12345" + cmd := exec.Command(dockerBinary, "run", "-p", port+":80", "busybox", "true") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err) + } + + // connect for 10 times here. This will trigger 10 EPIPES in the child + // process and kill it when it writes to a closed stdout/stderr + for i := 0; i < 10; i++ { + net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port)) + } + + listPs := exec.Command("sh", "-c", "ps ax | grep docker") + out, _, err = runCommandWithOutput(listPs) + if err != nil { + t.Errorf("list docker process failed with output %s, error %s", out, err) + } + if strings.Contains(out, "docker ") { + t.Errorf("Unexpected defunct docker process") + } + if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") { + t.Errorf("Failed to find docker-proxy process, got %s", out) + } + + logDone("run - proxy should work with unavailable port") +} + // Regression test for #7792 func TestRunMountOrdering(t *testing.T) { tmpDir, err := ioutil.TempDir("", "docker_nested_mount_test")