From f846ecdc77bb9ab4d44b41a2e403bcf579d6b6e9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 15:02:12 -0800 Subject: [PATCH] Make exec driver run a blocking command Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 69 ++++++++++++++++++++----------------- execdriver/chroot/driver.go | 20 ++++++++--- execdriver/driver.go | 9 ++--- execdriver/lxc/driver.go | 31 +++++++++++------ runtime.go | 6 ++-- 5 files changed, 82 insertions(+), 53 deletions(-) diff --git a/container.go b/container.go index d81702fd83..9fac0da587 100644 --- a/container.go +++ b/container.go @@ -308,10 +308,10 @@ func (container *Container) generateLXCConfig() error { return LxcTemplateCompiled.Execute(fo, container) } -func (container *Container) startPty() error { +func (container *Container) startPty(startCallback execdriver.StartCallback) (int, error) { ptyMaster, ptySlave, err := pty.Open() if err != nil { - return err + return -1, err } container.ptyMaster = ptyMaster container.process.Stdout = ptySlave @@ -336,20 +336,17 @@ func (container *Container) startPty() error { utils.Debugf("startPty: end of stdin pipe") }() } - if err := container.runtime.Start(container); err != nil { - return err - } - ptySlave.Close() - return nil + + return container.runtime.Run(container, startCallback) } -func (container *Container) start() error { +func (container *Container) start(startCallback execdriver.StartCallback) (int, error) { container.process.Stdout = container.stdout container.process.Stderr = container.stderr if container.Config.OpenStdin { stdin, err := container.process.StdinPipe() if err != nil { - return err + return -1, err } go func() { defer stdin.Close() @@ -358,7 +355,7 @@ func (container *Container) start() error { utils.Debugf("start: end of stdin pipe") }() } - return container.runtime.Start(container) + return container.runtime.Run(container, startCallback) } func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { @@ -689,7 +686,6 @@ func (container *Container) Start() (err error) { Network: en, Tty: container.Config.Tty, User: container.Config.User, - WaitLock: make(chan struct{}), SysInitPath: runtime.sysInitPath, } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} @@ -703,21 +699,26 @@ func (container *Container) Start() (err error) { } container.waitLock = make(chan struct{}) container.State.SetRunning(0) - go container.monitor() - if container.Config.Tty { - err = container.startPty() - } else { - err = container.start() - } - if err != nil { - return err + waitLock := make(chan struct{}) + f := func(process *execdriver.Process) { + container.State.SetRunning(process.Pid()) + if process.Tty { + if c, ok := process.Stdout.(io.Closer); ok { + c.Close() + } + } + if err := container.ToDisk(); err != nil { + utils.Debugf("%s", err) + } + close(waitLock) } - // TODO: @crosbymichael @creack - // find a way to update this - // container.State.SetRunning(container.process.Pid()) - container.ToDisk() + go container.monitor(f) + + // Start should not return until the process is actually running + <-waitLock + return nil } @@ -1091,17 +1092,24 @@ func (container *Container) releaseNetwork() { container.NetworkSettings = &NetworkSettings{} } -func (container *Container) monitor() { - // Wait for the program to exit +func (container *Container) monitor(f execdriver.StartCallback) { + var ( + err error + exitCode int + ) + if container.process == nil { - if err := container.runtime.Wait(container, 0); err != nil { - utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) - } + // This happends when you have a GHOST container with lxc + err = container.runtime.Wait(container, 0) } else { - <-container.process.WaitLock + if container.Config.Tty { + exitCode, err = container.startPty(f) + } else { + exitCode, err = container.start(f) + } } - if err := container.process.WaitError; err != nil { + if err != nil { //TODO: @crosbymichael @creack report error // Since non-zero exit status and signal terminations will cause err to be non-nil, // we have to actually discard it. Still, log it anyway, just in case. utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) @@ -1118,7 +1126,6 @@ func (container *Container) monitor() { container.stdin, container.stdinPipe = io.Pipe() } - exitCode := container.process.GetExitCode() container.State.SetStopped(exitCode) close(container.waitLock) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index b404f180e5..eb2525be6d 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -17,7 +17,7 @@ func (d *driver) String() string { return "chroot" } -func (d *driver) Start(c *execdriver.Process) error { +func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { params := []string{ "chroot", c.Rootfs, @@ -40,17 +40,27 @@ func (d *driver) Start(c *execdriver.Process) error { c.Args = append([]string{name}, arg...) if err := c.Start(); err != nil { - return err + return -1, err } + var ( + waitErr error + waitLock = make(chan struct{}) + ) go func() { if err := c.Wait(); err != nil { - c.WaitError = err + waitErr = err } - close(c.WaitLock) + close(waitLock) }() - return nil + if startCallback != nil { + startCallback(c) + } + + <-waitLock + + return c.GetExitCode(), waitErr } func (d *driver) Kill(p *execdriver.Process, sig int) error { diff --git a/execdriver/driver.go b/execdriver/driver.go index 04c52161d4..c38dc96a91 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -6,10 +6,13 @@ import ( "time" ) +type StartCallback func(*Process) + type Driver interface { - Start(c *Process) error + Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Process, sig int) error - Wait(id string, duration time.Duration) error // Wait on an out of process option - lxc ghosts + // TODO: @crosbymichael @creack wait should probably return the exit code + Wait(id string, duration time.Duration) error // Wait on an out of process...process - lxc ghosts Version() string String() string } @@ -38,8 +41,6 @@ type Process struct { Tty bool Network *Network // if network is nil then networking is disabled SysInitPath string - WaitLock chan struct{} - WaitError error } func (c *Process) Pid() int { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 7a4e754911..5ab6f8b824 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -45,7 +45,7 @@ func (d *driver) String() string { return "lxc" } -func (d *driver) Start(c *execdriver.Process) error { +func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { params := []string{ startPath, "-n", c.ID, @@ -111,21 +111,32 @@ func (d *driver) Start(c *execdriver.Process) error { c.Args = append([]string{name}, arg...) if err := c.Start(); err != nil { - return err + return -1, err } + var ( + waitErr error + waitLock = make(chan struct{}) + ) go func() { if err := c.Wait(); err != nil { - c.WaitError = err + waitErr = err } - close(c.WaitLock) + close(waitLock) }() - // Poll for running - if err := d.waitForStart(c); err != nil { - return err + // Poll lxc for RUNNING status + if err := d.waitForStart(c, waitLock); err != nil { + return -1, err } - return nil + + if startCallback != nil { + startCallback(c) + } + + <-waitLock + + return c.GetExitCode(), waitErr } func (d *driver) Kill(c *execdriver.Process, sig int) error { @@ -171,7 +182,7 @@ func (d *driver) kill(c *execdriver.Process, sig int) error { return nil } -func (d *driver) waitForStart(c *execdriver.Process) error { +func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) error { var ( err error output []byte @@ -182,7 +193,7 @@ func (d *driver) waitForStart(c *execdriver.Process) error { // the end of this loop for now := time.Now(); time.Since(now) < 5*time.Second; { select { - case <-c.WaitLock: + case <-waitLock: // If the process dies while waiting for it, just return if c.ProcessState != nil && c.ProcessState.Exited() { return nil diff --git a/runtime.go b/runtime.go index 0ae23740c3..ec32f75456 100644 --- a/runtime.go +++ b/runtime.go @@ -192,7 +192,7 @@ func (runtime *Runtime) Register(container *Container) error { } container.waitLock = make(chan struct{}) - go container.monitor() + go container.monitor(nil) } } return nil @@ -841,8 +841,8 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { return archive.ExportChanges(cDir, changes) } -func (runtime *Runtime) Start(c *Container) error { - return runtime.execDriver.Start(c.process) +func (runtime *Runtime) Run(c *Container, startCallback execdriver.StartCallback) (int, error) { + return runtime.execDriver.Run(c.process, startCallback) } func (runtime *Runtime) Kill(c *Container, sig int) error {