Browse code

Bug fix: `docker run -i --restart always` hangs.

e.g.
```
$ docker run -i --restart always busybox sh
pwd
/
exit 11

<...hang...>
```

This is because Attach(daemon side) and Run(client side) both hangs on
WaitStop, if container is restarted too quickly, wait won't have chance
to get exit signal.

Signed-off-by: Zhang Wei <zhangwei555@huawei.com>

Zhang Wei authored on 2016/05/17 11:30:06
Showing 3 changed files
... ...
@@ -287,7 +287,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
287 287
 		}
288 288
 	} else {
289 289
 		// No Autoremove: Simply retrieve the exit code
290
-		if !config.Tty {
290
+		if !config.Tty && hostConfig.RestartPolicy.IsNone() {
291 291
 			// In non-TTY mode, we can't detach, so we must wait for container exit
292 292
 			if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
293 293
 				return err
... ...
@@ -119,6 +119,15 @@ func (daemon *Daemon) containerAttach(c *container.Container, stdin io.ReadClose
119 119
 			}()
120 120
 			stdinPipe = r
121 121
 		}
122
+
123
+		waitChan := make(chan struct{})
124
+		if c.Config.StdinOnce && !c.Config.Tty {
125
+			go func() {
126
+				c.WaitStop(-1 * time.Second)
127
+				close(waitChan)
128
+			}()
129
+		}
130
+
122 131
 		err := <-c.Attach(stdinPipe, stdout, stderr, keys)
123 132
 		if err != nil {
124 133
 			if _, ok := err.(container.DetachError); ok {
... ...
@@ -131,7 +140,7 @@ func (daemon *Daemon) containerAttach(c *container.Container, stdin io.ReadClose
131 131
 		// If we are in stdinonce mode, wait for the process to end
132 132
 		// otherwise, simply return
133 133
 		if c.Config.StdinOnce && !c.Config.Tty {
134
-			c.WaitStop(-1 * time.Second)
134
+			<-waitChan
135 135
 		}
136 136
 	}
137 137
 	return nil
... ...
@@ -1828,6 +1828,37 @@ func (s *DockerSuite) TestRunExitOnStdinClose(c *check.C) {
1828 1828
 	}
1829 1829
 }
1830 1830
 
1831
+// Test run -i --restart xxx doesn't hang
1832
+func (s *DockerSuite) TestRunInteractiveWithRestartPolicy(c *check.C) {
1833
+	name := "test-inter-restart"
1834
+	runCmd := exec.Command(dockerBinary, "run", "-i", "--name", name, "--restart=always", "busybox", "sh")
1835
+
1836
+	stdin, err := runCmd.StdinPipe()
1837
+	c.Assert(err, checker.IsNil)
1838
+
1839
+	err = runCmd.Start()
1840
+	c.Assert(err, checker.IsNil)
1841
+	c.Assert(waitRun(name), check.IsNil)
1842
+
1843
+	_, err = stdin.Write([]byte("exit 11\n"))
1844
+	c.Assert(err, checker.IsNil)
1845
+
1846
+	finish := make(chan error)
1847
+	go func() {
1848
+		finish <- runCmd.Wait()
1849
+		close(finish)
1850
+	}()
1851
+	delay := 10 * time.Second
1852
+	select {
1853
+	case <-finish:
1854
+	case <-time.After(delay):
1855
+		c.Fatal("run -i --restart hangs")
1856
+	}
1857
+
1858
+	c.Assert(waitRun(name), check.IsNil)
1859
+	dockerCmd(c, "stop", name)
1860
+}
1861
+
1831 1862
 // Test for #2267
1832 1863
 func (s *DockerSuite) TestRunWriteHostsFileAndNotCommit(c *check.C) {
1833 1864
 	// Cannot run on Windows as Windows does not support diff.