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 <dqminh89@gmail.com> (github: dqminh)
| ... | ... |
@@ -36,16 +36,18 @@ type proxyCommand struct {
|
| 36 | 36 |
|
| 37 | 37 |
// execProxy is the reexec function that is registered to start the userland proxies |
| 38 | 38 |
func execProxy() {
|
| 39 |
+ f := os.NewFile(3, "signal-parent") |
|
| 39 | 40 |
host, container := parseHostContainerAddrs() |
| 40 | 41 |
|
| 41 | 42 |
p, err := proxy.NewProxy(host, container) |
| 42 | 43 |
if err != nil {
|
| 43 |
- os.Stdout.WriteString("1\n")
|
|
| 44 |
- fmt.Fprint(os.Stderr, err) |
|
| 44 |
+ fmt.Fprintf(f, "1\n%s", err) |
|
| 45 |
+ f.Close() |
|
| 45 | 46 |
os.Exit(1) |
| 46 | 47 |
} |
| 47 | 48 |
go handleStopSignals(p) |
| 48 |
- os.Stdout.WriteString("0\n")
|
|
| 49 |
+ fmt.Fprint(f, "0\n") |
|
| 50 |
+ f.Close() |
|
| 49 | 51 |
|
| 50 | 52 |
// Run will block until the proxy stops |
| 51 | 53 |
p.Run() |
| ... | ... |
@@ -111,27 +113,24 @@ func NewProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net. |
| 111 | 111 |
} |
| 112 | 112 |
|
| 113 | 113 |
func (p *proxyCommand) Start() error {
|
| 114 |
- stdout, err := p.cmd.StdoutPipe() |
|
| 114 |
+ r, w, err := os.Pipe() |
|
| 115 | 115 |
if err != nil {
|
| 116 |
- return err |
|
| 117 |
- } |
|
| 118 |
- defer stdout.Close() |
|
| 119 |
- stderr, err := p.cmd.StderrPipe() |
|
| 120 |
- if err != nil {
|
|
| 121 |
- return err |
|
| 116 |
+ return fmt.Errorf("proxy unable to open os.Pipe %s", err)
|
|
| 122 | 117 |
} |
| 123 |
- defer stderr.Close() |
|
| 118 |
+ defer r.Close() |
|
| 119 |
+ p.cmd.ExtraFiles = []*os.File{w}
|
|
| 124 | 120 |
if err := p.cmd.Start(); err != nil {
|
| 125 | 121 |
return err |
| 126 | 122 |
} |
| 123 |
+ w.Close() |
|
| 127 | 124 |
|
| 128 | 125 |
errchan := make(chan error, 1) |
| 129 | 126 |
go func() {
|
| 130 | 127 |
buf := make([]byte, 2) |
| 131 |
- stdout.Read(buf) |
|
| 128 |
+ r.Read(buf) |
|
| 132 | 129 |
|
| 133 | 130 |
if string(buf) != "0\n" {
|
| 134 |
- errStr, _ := ioutil.ReadAll(stderr) |
|
| 131 |
+ errStr, _ := ioutil.ReadAll(r) |
|
| 135 | 132 |
errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
|
| 136 | 133 |
return |
| 137 | 134 |
} |
| ... | ... |
@@ -2093,6 +2093,39 @@ func TestRunPortInUse(t *testing.T) {
|
| 2093 | 2093 |
logDone("run - fail if port already in use")
|
| 2094 | 2094 |
} |
| 2095 | 2095 |
|
| 2096 |
+// https://github.com/docker/docker/issues/8428 |
|
| 2097 |
+func TestRunPortProxy(t *testing.T) {
|
|
| 2098 |
+ defer deleteAllContainers() |
|
| 2099 |
+ |
|
| 2100 |
+ port := "12345" |
|
| 2101 |
+ cmd := exec.Command(dockerBinary, "run", "-p", port+":80", "busybox", "true") |
|
| 2102 |
+ |
|
| 2103 |
+ out, _, err := runCommandWithOutput(cmd) |
|
| 2104 |
+ if err != nil {
|
|
| 2105 |
+ t.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err)
|
|
| 2106 |
+ } |
|
| 2107 |
+ |
|
| 2108 |
+ // connect for 10 times here. This will trigger 10 EPIPES in the child |
|
| 2109 |
+ // process and kill it when it writes to a closed stdout/stderr |
|
| 2110 |
+ for i := 0; i < 10; i++ {
|
|
| 2111 |
+ net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port))
|
|
| 2112 |
+ } |
|
| 2113 |
+ |
|
| 2114 |
+ listPs := exec.Command("sh", "-c", "ps ax | grep docker")
|
|
| 2115 |
+ out, _, err = runCommandWithOutput(listPs) |
|
| 2116 |
+ if err != nil {
|
|
| 2117 |
+ t.Errorf("list docker process failed with output %s, error %s", out, err)
|
|
| 2118 |
+ } |
|
| 2119 |
+ if strings.Contains(out, "docker <defunct>") {
|
|
| 2120 |
+ t.Errorf("Unexpected defunct docker process")
|
|
| 2121 |
+ } |
|
| 2122 |
+ if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") {
|
|
| 2123 |
+ t.Errorf("Failed to find docker-proxy process, got %s", out)
|
|
| 2124 |
+ } |
|
| 2125 |
+ |
|
| 2126 |
+ logDone("run - proxy should work with unavailable port")
|
|
| 2127 |
+} |
|
| 2128 |
+ |
|
| 2096 | 2129 |
// Regression test for #7792 |
| 2097 | 2130 |
func TestRunMountOrdering(t *testing.T) {
|
| 2098 | 2131 |
tmpDir, err := ioutil.TempDir("", "docker_nested_mount_test")
|