github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/integration-cli/docker_cli_exec_test.go (about) 1 // +build !test_no_exec 2 3 package main 4 5 import ( 6 "bufio" 7 "context" 8 "fmt" 9 "os" 10 "os/exec" 11 "reflect" 12 "runtime" 13 "sort" 14 "strconv" 15 "strings" 16 "sync" 17 "time" 18 19 "github.com/docker/docker/client" 20 "github.com/docker/docker/integration-cli/cli" 21 "github.com/docker/docker/integration-cli/cli/build" 22 "github.com/docker/docker/pkg/parsers/kernel" 23 "github.com/go-check/check" 24 "gotest.tools/assert" 25 is "gotest.tools/assert/cmp" 26 "gotest.tools/icmd" 27 ) 28 29 func (s *DockerSuite) TestExec(c *check.C) { 30 testRequires(c, DaemonIsLinux) 31 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 32 assert.NilError(c, waitRun(strings.TrimSpace(out))) 33 34 out, _ = dockerCmd(c, "exec", "testing", "cat", "/tmp/file") 35 assert.Equal(c, strings.Trim(out, "\r\n"), "test") 36 } 37 38 func (s *DockerSuite) TestExecInteractive(c *check.C) { 39 testRequires(c, DaemonIsLinux) 40 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 41 42 execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") 43 stdin, err := execCmd.StdinPipe() 44 assert.NilError(c, err) 45 stdout, err := execCmd.StdoutPipe() 46 assert.NilError(c, err) 47 48 err = execCmd.Start() 49 assert.NilError(c, err) 50 _, err = stdin.Write([]byte("cat /tmp/file\n")) 51 assert.NilError(c, err) 52 53 r := bufio.NewReader(stdout) 54 line, err := r.ReadString('\n') 55 assert.NilError(c, err) 56 line = strings.TrimSpace(line) 57 assert.Equal(c, line, "test") 58 err = stdin.Close() 59 assert.NilError(c, err) 60 errChan := make(chan error) 61 go func() { 62 errChan <- execCmd.Wait() 63 close(errChan) 64 }() 65 select { 66 case err := <-errChan: 67 assert.NilError(c, err) 68 case <-time.After(1 * time.Second): 69 c.Fatal("docker exec failed to exit on stdin close") 70 } 71 72 } 73 74 func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { 75 out := runSleepingContainer(c) 76 cleanedContainerID := strings.TrimSpace(out) 77 assert.NilError(c, waitRun(cleanedContainerID)) 78 dockerCmd(c, "restart", cleanedContainerID) 79 assert.NilError(c, waitRun(cleanedContainerID)) 80 81 out, _ = dockerCmd(c, "exec", cleanedContainerID, "echo", "hello") 82 assert.Equal(c, strings.TrimSpace(out), "hello") 83 } 84 85 func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) { 86 // TODO Windows CI: Requires a little work to get this ported. 87 testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon) 88 s.d.StartWithBusybox(c) 89 90 out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top") 91 assert.NilError(c, err, "Could not run top: %s", out) 92 93 s.d.Restart(c) 94 95 out, err = s.d.Cmd("start", "top") 96 assert.NilError(c, err, "Could not start top after daemon restart: %s", out) 97 98 out, err = s.d.Cmd("exec", "top", "echo", "hello") 99 assert.NilError(c, err, "Could not exec on container top: %s", out) 100 assert.Equal(c, strings.TrimSpace(out), "hello") 101 } 102 103 // Regression test for #9155, #9044 104 func (s *DockerSuite) TestExecEnv(c *check.C) { 105 // TODO Windows CI: This one is interesting and may just end up being a feature 106 // difference between Windows and Linux. On Windows, the environment is passed 107 // into the process that is launched, not into the machine environment. Hence 108 // a subsequent exec will not have LALA set/ 109 testRequires(c, DaemonIsLinux) 110 runSleepingContainer(c, "-e", "LALA=value1", "-e", "LALA=value2", "-d", "--name", "testing") 111 assert.NilError(c, waitRun("testing")) 112 113 out, _ := dockerCmd(c, "exec", "testing", "env") 114 assert.Check(c, !strings.Contains(out, "LALA=value1")) 115 assert.Check(c, strings.Contains(out, "LALA=value2")) 116 assert.Check(c, strings.Contains(out, "HOME=/root")) 117 } 118 119 func (s *DockerSuite) TestExecSetEnv(c *check.C) { 120 testRequires(c, DaemonIsLinux) 121 runSleepingContainer(c, "-e", "HOME=/root", "-d", "--name", "testing") 122 assert.NilError(c, waitRun("testing")) 123 124 out, _ := dockerCmd(c, "exec", "-e", "HOME=/another", "-e", "ABC=xyz", "testing", "env") 125 assert.Check(c, !strings.Contains(out, "HOME=/root")) 126 assert.Check(c, strings.Contains(out, "HOME=/another")) 127 assert.Check(c, strings.Contains(out, "ABC=xyz")) 128 } 129 130 func (s *DockerSuite) TestExecExitStatus(c *check.C) { 131 runSleepingContainer(c, "-d", "--name", "top") 132 133 result := icmd.RunCommand(dockerBinary, "exec", "top", "sh", "-c", "exit 23") 134 result.Assert(c, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) 135 } 136 137 func (s *DockerSuite) TestExecPausedContainer(c *check.C) { 138 testRequires(c, IsPausable) 139 140 out := runSleepingContainer(c, "-d", "--name", "testing") 141 ContainerID := strings.TrimSpace(out) 142 143 dockerCmd(c, "pause", "testing") 144 out, _, err := dockerCmdWithError("exec", ContainerID, "echo", "hello") 145 assert.ErrorContains(c, err, "", "container should fail to exec new command if it is paused") 146 147 expected := ContainerID + " is paused, unpause the container before exec" 148 assert.Assert(c, is.Contains(out, expected), "container should not exec new command if it is paused") 149 } 150 151 // regression test for #9476 152 func (s *DockerSuite) TestExecTTYCloseStdin(c *check.C) { 153 // TODO Windows CI: This requires some work to port to Windows. 154 testRequires(c, DaemonIsLinux) 155 dockerCmd(c, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") 156 157 cmd := exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") 158 stdinRw, err := cmd.StdinPipe() 159 assert.NilError(c, err) 160 161 stdinRw.Write([]byte("test")) 162 stdinRw.Close() 163 164 out, _, err := runCommandWithOutput(cmd) 165 assert.NilError(c, err, out) 166 167 out, _ = dockerCmd(c, "top", "exec_tty_stdin") 168 outArr := strings.Split(out, "\n") 169 assert.Assert(c, len(outArr) <= 3, "exec process left running") 170 assert.Assert(c, !strings.Contains(out, "nsenter-exec")) 171 } 172 173 func (s *DockerSuite) TestExecTTYWithoutStdin(c *check.C) { 174 out, _ := dockerCmd(c, "run", "-d", "-ti", "busybox") 175 id := strings.TrimSpace(out) 176 assert.NilError(c, waitRun(id)) 177 178 errChan := make(chan error) 179 go func() { 180 defer close(errChan) 181 182 cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") 183 if _, err := cmd.StdinPipe(); err != nil { 184 errChan <- err 185 return 186 } 187 188 expected := "the input device is not a TTY" 189 if runtime.GOOS == "windows" { 190 expected += ". If you are using mintty, try prefixing the command with 'winpty'" 191 } 192 if out, _, err := runCommandWithOutput(cmd); err == nil { 193 errChan <- fmt.Errorf("exec should have failed") 194 return 195 } else if !strings.Contains(out, expected) { 196 errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) 197 return 198 } 199 }() 200 201 select { 202 case err := <-errChan: 203 assert.NilError(c, err) 204 case <-time.After(3 * time.Second): 205 c.Fatal("exec is running but should have failed") 206 } 207 } 208 209 // FIXME(vdemeester) this should be a unit tests on cli/command/container package 210 func (s *DockerSuite) TestExecParseError(c *check.C) { 211 // TODO Windows CI: Requires some extra work. Consider copying the 212 // runSleepingContainer helper to have an exec version. 213 testRequires(c, DaemonIsLinux) 214 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 215 216 // Test normal (non-detached) case first 217 icmd.RunCommand(dockerBinary, "exec", "top").Assert(c, icmd.Expected{ 218 ExitCode: 1, 219 Error: "exit status 1", 220 Err: "See 'docker exec --help'", 221 }) 222 } 223 224 func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { 225 // TODO Windows CI: Requires some extra work. Consider copying the 226 // runSleepingContainer helper to have an exec version. 227 testRequires(c, DaemonIsLinux) 228 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 229 230 result := icmd.StartCmd(icmd.Command(dockerBinary, "exec", "testing", "top")) 231 result.Assert(c, icmd.Success) 232 go icmd.WaitOnCmd(0, result) 233 234 type dstop struct { 235 out string 236 err error 237 } 238 ch := make(chan dstop) 239 go func() { 240 result := icmd.RunCommand(dockerBinary, "stop", "testing") 241 ch <- dstop{result.Combined(), result.Error} 242 close(ch) 243 }() 244 select { 245 case <-time.After(3 * time.Second): 246 c.Fatal("Container stop timed out") 247 case s := <-ch: 248 assert.NilError(c, s.err) 249 } 250 } 251 252 func (s *DockerSuite) TestExecCgroup(c *check.C) { 253 // Not applicable on Windows - using Linux specific functionality 254 testRequires(c, NotUserNamespace) 255 testRequires(c, DaemonIsLinux) 256 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 257 258 out, _ := dockerCmd(c, "exec", "testing", "cat", "/proc/1/cgroup") 259 containerCgroups := sort.StringSlice(strings.Split(out, "\n")) 260 261 var wg sync.WaitGroup 262 var mu sync.Mutex 263 var execCgroups []sort.StringSlice 264 errChan := make(chan error) 265 // exec a few times concurrently to get consistent failure 266 for i := 0; i < 5; i++ { 267 wg.Add(1) 268 go func() { 269 out, _, err := dockerCmdWithError("exec", "testing", "cat", "/proc/self/cgroup") 270 if err != nil { 271 errChan <- err 272 return 273 } 274 cg := sort.StringSlice(strings.Split(out, "\n")) 275 276 mu.Lock() 277 execCgroups = append(execCgroups, cg) 278 mu.Unlock() 279 wg.Done() 280 }() 281 } 282 wg.Wait() 283 close(errChan) 284 285 for err := range errChan { 286 assert.NilError(c, err) 287 } 288 289 for _, cg := range execCgroups { 290 if !reflect.DeepEqual(cg, containerCgroups) { 291 fmt.Println("exec cgroups:") 292 for _, name := range cg { 293 fmt.Printf(" %s\n", name) 294 } 295 296 fmt.Println("container cgroups:") 297 for _, name := range containerCgroups { 298 fmt.Printf(" %s\n", name) 299 } 300 c.Fatal("cgroups mismatched") 301 } 302 } 303 } 304 305 func (s *DockerSuite) TestExecInspectID(c *check.C) { 306 out := runSleepingContainer(c, "-d") 307 id := strings.TrimSuffix(out, "\n") 308 309 out = inspectField(c, id, "ExecIDs") 310 assert.Equal(c, out, "[]", "ExecIDs should be empty, got: %s", out) 311 312 // Start an exec, have it block waiting so we can do some checking 313 cmd := exec.Command(dockerBinary, "exec", id, "sh", "-c", 314 "while ! test -e /execid1; do sleep 1; done") 315 316 err := cmd.Start() 317 assert.NilError(c, err, "failed to start the exec cmd") 318 319 // Give the exec 10 chances/seconds to start then give up and stop the test 320 tries := 10 321 for i := 0; i < tries; i++ { 322 // Since its still running we should see exec as part of the container 323 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 324 325 if out != "[]" && out != "<no value>" { 326 break 327 } 328 assert.Check(c, i+1 != tries, "ExecIDs still empty after 10 second") 329 time.Sleep(1 * time.Second) 330 } 331 332 // Save execID for later 333 execID, err := inspectFilter(id, "index .ExecIDs 0") 334 assert.NilError(c, err, "failed to get the exec id") 335 336 // End the exec by creating the missing file 337 err = exec.Command(dockerBinary, "exec", id, "sh", "-c", "touch /execid1").Run() 338 assert.NilError(c, err, "failed to run the 2nd exec cmd") 339 340 // Wait for 1st exec to complete 341 cmd.Wait() 342 343 // Give the exec 10 chances/seconds to stop then give up and stop the test 344 for i := 0; i < tries; i++ { 345 // Since its still running we should see exec as part of the container 346 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 347 348 if out == "[]" { 349 break 350 } 351 assert.Check(c, i+1 != tries, "ExecIDs still empty after 10 second") 352 time.Sleep(1 * time.Second) 353 } 354 355 // But we should still be able to query the execID 356 cli, err := client.NewClientWithOpts(client.FromEnv) 357 assert.NilError(c, err) 358 defer cli.Close() 359 360 _, err = cli.ContainerExecInspect(context.Background(), execID) 361 assert.NilError(c, err) 362 363 // Now delete the container and then an 'inspect' on the exec should 364 // result in a 404 (not 'container not running') 365 out, ec := dockerCmd(c, "rm", "-f", id) 366 assert.Equal(c, ec, 0, "error removing container: %s", out) 367 368 _, err = cli.ContainerExecInspect(context.Background(), execID) 369 assert.ErrorContains(c, err, "No such exec instance") 370 } 371 372 func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { 373 // Problematic on Windows as Windows does not support links 374 testRequires(c, DaemonIsLinux) 375 var out string 376 out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") 377 idA := strings.TrimSpace(out) 378 assert.Assert(c, idA != "", "%s, id should not be nil", out) 379 out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") 380 idB := strings.TrimSpace(out) 381 assert.Assert(c, idB != "", "%s, id should not be nil", out) 382 383 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 384 dockerCmd(c, "rename", "container1", "container_new") 385 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 386 } 387 388 func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { 389 // Not applicable on Windows to Windows CI. 390 testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux) 391 for _, fn := range []string{"resolv.conf", "hosts"} { 392 containers := cli.DockerCmd(c, "ps", "-q", "-a").Combined() 393 if containers != "" { 394 cli.DockerCmd(c, append([]string{"rm", "-fv"}, strings.Split(strings.TrimSpace(containers), "\n")...)...) 395 } 396 397 content := runCommandAndReadContainerFile(c, fn, dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn)) 398 399 assert.Equal(c, strings.TrimSpace(string(content)), "success", "Content was not what was modified in the container", string(content)) 400 401 out, _ := dockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top") 402 contID := strings.TrimSpace(out) 403 netFilePath := containerStorageFile(contID, fn) 404 405 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) 406 assert.NilError(c, err) 407 408 if _, err := f.Seek(0, 0); err != nil { 409 f.Close() 410 c.Fatal(err) 411 } 412 413 if err := f.Truncate(0); err != nil { 414 f.Close() 415 c.Fatal(err) 416 } 417 418 if _, err := f.Write([]byte("success2\n")); err != nil { 419 f.Close() 420 c.Fatal(err) 421 } 422 f.Close() 423 424 res, _ := dockerCmd(c, "exec", contID, "cat", "/etc/"+fn) 425 assert.Equal(c, res, "success2\n") 426 } 427 } 428 429 func (s *DockerSuite) TestExecWithUser(c *check.C) { 430 // TODO Windows CI: This may be fixable in the future once Windows 431 // supports users 432 testRequires(c, DaemonIsLinux) 433 dockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 434 435 out, _ := dockerCmd(c, "exec", "-u", "1", "parent", "id") 436 assert.Assert(c, strings.Contains(out, "uid=1(daemon) gid=1(daemon)")) 437 438 out, _ = dockerCmd(c, "exec", "-u", "root", "parent", "id") 439 assert.Assert(c, strings.Contains(out, "uid=0(root) gid=0(root)"), "exec with user by id expected daemon user got %s", out) 440 } 441 442 func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { 443 // Not applicable on Windows 444 testRequires(c, DaemonIsLinux, NotUserNamespace) 445 // Start main loop which attempts mknod repeatedly 446 dockerCmd(c, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "sh", "-c", `while (true); do if [ -e /exec_priv ]; then cat /exec_priv && mknod /tmp/sda b 8 0 && echo "Success"; else echo "Privileged exec has not run yet"; fi; usleep 10000; done`) 447 448 // Check exec mknod doesn't work 449 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16").Assert(c, icmd.Expected{ 450 ExitCode: 1, 451 Err: "Operation not permitted", 452 }) 453 454 // Check exec mknod does work with --privileged 455 result := icmd.RunCommand(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", `echo "Running exec --privileged" > /exec_priv && mknod /tmp/sdb b 8 16 && usleep 50000 && echo "Finished exec --privileged" > /exec_priv && echo ok`) 456 result.Assert(c, icmd.Success) 457 458 actual := strings.TrimSpace(result.Combined()) 459 assert.Equal(c, actual, "ok", "exec mknod in --cap-drop=ALL container with --privileged failed, output: %q", result.Combined()) 460 461 // Check subsequent unprivileged exec cannot mknod 462 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32").Assert(c, icmd.Expected{ 463 ExitCode: 1, 464 Err: "Operation not permitted", 465 }) 466 // Confirm at no point was mknod allowed 467 result = icmd.RunCommand(dockerBinary, "logs", "parent") 468 result.Assert(c, icmd.Success) 469 assert.Assert(c, !strings.Contains(result.Combined(), "Success")) 470 } 471 472 func (s *DockerSuite) TestExecWithImageUser(c *check.C) { 473 // Not applicable on Windows 474 testRequires(c, DaemonIsLinux) 475 name := "testbuilduser" 476 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 477 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 478 USER dockerio`)) 479 dockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 480 481 out, _ := dockerCmd(c, "exec", "dockerioexec", "whoami") 482 assert.Assert(c, strings.Contains(out, "dockerio"), "exec with user by id expected dockerio user got %s", out) 483 } 484 485 func (s *DockerSuite) TestExecOnReadonlyContainer(c *check.C) { 486 // Windows does not support read-only 487 // --read-only + userns has remount issues 488 testRequires(c, DaemonIsLinux, NotUserNamespace) 489 dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 490 dockerCmd(c, "exec", "parent", "true") 491 } 492 493 func (s *DockerSuite) TestExecUlimits(c *check.C) { 494 testRequires(c, DaemonIsLinux) 495 name := "testexeculimits" 496 runSleepingContainer(c, "-d", "--ulimit", "nofile=511:511", "--name", name) 497 assert.NilError(c, waitRun(name)) 498 499 out, _, err := dockerCmdWithError("exec", name, "sh", "-c", "ulimit -n") 500 assert.NilError(c, err) 501 assert.Equal(c, strings.TrimSpace(out), "511") 502 } 503 504 // #15750 505 func (s *DockerSuite) TestExecStartFails(c *check.C) { 506 // TODO Windows CI. This test should be portable. Figure out why it fails 507 // currently. 508 testRequires(c, DaemonIsLinux) 509 name := "exec-15750" 510 runSleepingContainer(c, "-d", "--name", name) 511 assert.NilError(c, waitRun(name)) 512 513 out, _, err := dockerCmdWithError("exec", name, "no-such-cmd") 514 assert.ErrorContains(c, err, "", out) 515 assert.Assert(c, strings.Contains(out, "executable file not found")) 516 } 517 518 // Fix regression in https://github.com/docker/docker/pull/26461#issuecomment-250287297 519 func (s *DockerSuite) TestExecWindowsPathNotWiped(c *check.C) { 520 testRequires(c, DaemonIsWindows) 521 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", minimalBaseImage(), "powershell", "start-sleep", "60") 522 assert.NilError(c, waitRun(strings.TrimSpace(out))) 523 524 out, _ = dockerCmd(c, "exec", "testing", "powershell", "write-host", "$env:PATH") 525 out = strings.ToLower(strings.Trim(out, "\r\n")) 526 assert.Assert(c, strings.Contains(out, `windowspowershell\v1.0`)) 527 } 528 529 func (s *DockerSuite) TestExecEnvLinksHost(c *check.C) { 530 testRequires(c, DaemonIsLinux) 531 runSleepingContainer(c, "-d", "--name", "foo") 532 runSleepingContainer(c, "-d", "--link", "foo:db", "--hostname", "myhost", "--name", "bar") 533 out, _ := dockerCmd(c, "exec", "bar", "env") 534 assert.Check(c, is.Contains(out, "HOSTNAME=myhost")) 535 assert.Check(c, is.Contains(out, "DB_NAME=/bar/db")) 536 } 537 538 func (s *DockerSuite) TestExecWindowsOpenHandles(c *check.C) { 539 testRequires(c, DaemonIsWindows) 540 541 if runtime.GOOS == "windows" { 542 v, err := kernel.GetKernelVersion() 543 assert.NilError(c, err) 544 build, _ := strconv.Atoi(strings.Split(strings.SplitN(v.String(), " ", 3)[2][1:], ".")[0]) 545 if build >= 17743 { 546 c.Skip("Temporarily disabled on RS5 17743+ builds due to platform bug") 547 548 // This is being tracked internally. @jhowardmsft. Summary of failure 549 // from an email in early July 2018 below: 550 // 551 // Platform regression. In cmd.exe by the look of it. I can repro 552 // it outside of CI. It fails the same on 17681, 17676 and even as 553 // far back as 17663, over a month old. From investigating, I can see 554 // what's happening in the container, but not the reason. The test 555 // starts a long-running container based on the Windows busybox image. 556 // It then adds another process (docker exec) to that container to 557 // sleep. It loops waiting for two instances of busybox.exe running, 558 // and cmd.exe to quit. What's actually happening is that the second 559 // exec hangs indefinitely, and from docker top, I can see 560 // "OpenWith.exe" running. 561 562 //Manual repro would be 563 //# Start the first long-running container 564 //docker run --rm -d --name test busybox sleep 300 565 566 //# In another window, docker top test. There should be a single instance of busybox.exe running 567 //# In a third window, docker exec test cmd /c start sleep 10 NOTE THIS HANGS UNTIL 5 MIN TIMEOUT 568 //# In the second window, run docker top test. Note that OpenWith.exe is running, one cmd.exe and only one busybox. I would expect no "OpenWith" and two busybox.exe's. 569 } 570 } 571 572 runSleepingContainer(c, "-d", "--name", "test") 573 exec := make(chan bool) 574 go func() { 575 dockerCmd(c, "exec", "test", "cmd", "/c", "start sleep 10") 576 exec <- true 577 }() 578 579 count := 0 580 for { 581 top := make(chan string) 582 var out string 583 go func() { 584 out, _ := dockerCmd(c, "top", "test") 585 top <- out 586 }() 587 588 select { 589 case <-time.After(time.Second * 5): 590 c.Fatal("timed out waiting for top while exec is exiting") 591 case out = <-top: 592 break 593 } 594 595 if strings.Count(out, "busybox.exe") == 2 && !strings.Contains(out, "cmd.exe") { 596 // The initial exec process (cmd.exe) has exited, and both sleeps are currently running 597 break 598 } 599 count++ 600 if count >= 30 { 601 c.Fatal("too many retries") 602 } 603 time.Sleep(1 * time.Second) 604 } 605 606 inspect := make(chan bool) 607 go func() { 608 dockerCmd(c, "inspect", "test") 609 inspect <- true 610 }() 611 612 select { 613 case <-time.After(time.Second * 5): 614 c.Fatal("timed out waiting for inspect while exec is exiting") 615 case <-inspect: 616 break 617 } 618 619 // Ensure the background sleep is still running 620 out, _ := dockerCmd(c, "top", "test") 621 assert.Equal(c, strings.Count(out, "busybox.exe"), 2) 622 623 // The exec should exit when the background sleep exits 624 select { 625 case <-time.After(time.Second * 15): 626 c.Fatal("timed out waiting for async exec to exit") 627 case <-exec: 628 // Ensure the background sleep has actually exited 629 out, _ := dockerCmd(c, "top", "test") 630 assert.Equal(c, strings.Count(out, "busybox.exe"), 1) 631 break 632 } 633 }