Przeglądaj źródła

Make exec driver run a blocking command

Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)
Michael Crosby 11 lat temu
rodzic
commit
f846ecdc77
5 zmienionych plików z 82 dodań i 53 usunięć
  1. 38 31
      container.go
  2. 15 5
      execdriver/chroot/driver.go
  3. 5 4
      execdriver/driver.go
  4. 21 10
      execdriver/lxc/driver.go
  5. 3 3
      runtime.go

+ 38 - 31
container.go

@@ -308,10 +308,10 @@ func (container *Container) generateLXCConfig() error {
 	return LxcTemplateCompiled.Execute(fo, container)
 	return LxcTemplateCompiled.Execute(fo, container)
 }
 }
 
 
-func (container *Container) startPty() error {
+func (container *Container) startPty(startCallback execdriver.StartCallback) (int, error) {
 	ptyMaster, ptySlave, err := pty.Open()
 	ptyMaster, ptySlave, err := pty.Open()
 	if err != nil {
 	if err != nil {
-		return err
+		return -1, err
 	}
 	}
 	container.ptyMaster = ptyMaster
 	container.ptyMaster = ptyMaster
 	container.process.Stdout = ptySlave
 	container.process.Stdout = ptySlave
@@ -336,20 +336,17 @@ func (container *Container) startPty() error {
 			utils.Debugf("startPty: end of stdin pipe")
 			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.Stdout = container.stdout
 	container.process.Stderr = container.stderr
 	container.process.Stderr = container.stderr
 	if container.Config.OpenStdin {
 	if container.Config.OpenStdin {
 		stdin, err := container.process.StdinPipe()
 		stdin, err := container.process.StdinPipe()
 		if err != nil {
 		if err != nil {
-			return err
+			return -1, err
 		}
 		}
 		go func() {
 		go func() {
 			defer stdin.Close()
 			defer stdin.Close()
@@ -358,7 +355,7 @@ func (container *Container) start() error {
 			utils.Debugf("start: end of stdin pipe")
 			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 {
 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,
 		Network:     en,
 		Tty:         container.Config.Tty,
 		Tty:         container.Config.Tty,
 		User:        container.Config.User,
 		User:        container.Config.User,
-		WaitLock:    make(chan struct{}),
 		SysInitPath: runtime.sysInitPath,
 		SysInitPath: runtime.sysInitPath,
 	}
 	}
 	container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
 	container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
@@ -703,21 +699,26 @@ func (container *Container) Start() (err error) {
 	}
 	}
 	container.waitLock = make(chan struct{})
 	container.waitLock = make(chan struct{})
 	container.State.SetRunning(0)
 	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
 	return nil
 }
 }
 
 
@@ -1091,17 +1092,24 @@ func (container *Container) releaseNetwork() {
 	container.NetworkSettings = &NetworkSettings{}
 	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 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 {
 	} 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,
 		// 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.
 		// 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)
 		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()
 		container.stdin, container.stdinPipe = io.Pipe()
 	}
 	}
 
 
-	exitCode := container.process.GetExitCode()
 	container.State.SetStopped(exitCode)
 	container.State.SetStopped(exitCode)
 
 
 	close(container.waitLock)
 	close(container.waitLock)

+ 15 - 5
execdriver/chroot/driver.go

@@ -17,7 +17,7 @@ func (d *driver) String() string {
 	return "chroot"
 	return "chroot"
 }
 }
 
 
-func (d *driver) Start(c *execdriver.Process) error {
+func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) {
 	params := []string{
 	params := []string{
 		"chroot",
 		"chroot",
 		c.Rootfs,
 		c.Rootfs,
@@ -40,17 +40,27 @@ func (d *driver) Start(c *execdriver.Process) error {
 	c.Args = append([]string{name}, arg...)
 	c.Args = append([]string{name}, arg...)
 
 
 	if err := c.Start(); err != nil {
 	if err := c.Start(); err != nil {
-		return err
+		return -1, err
 	}
 	}
 
 
+	var (
+		waitErr  error
+		waitLock = make(chan struct{})
+	)
 	go func() {
 	go func() {
 		if err := c.Wait(); err != nil {
 		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 {
 func (d *driver) Kill(p *execdriver.Process, sig int) error {

+ 5 - 4
execdriver/driver.go

@@ -6,10 +6,13 @@ import (
 	"time"
 	"time"
 )
 )
 
 
+type StartCallback func(*Process)
+
 type Driver interface {
 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
 	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
 	Version() string
 	String() string
 	String() string
 }
 }
@@ -38,8 +41,6 @@ type Process struct {
 	Tty         bool
 	Tty         bool
 	Network     *Network // if network is nil then networking is disabled
 	Network     *Network // if network is nil then networking is disabled
 	SysInitPath string
 	SysInitPath string
-	WaitLock    chan struct{}
-	WaitError   error
 }
 }
 
 
 func (c *Process) Pid() int {
 func (c *Process) Pid() int {

+ 21 - 10
execdriver/lxc/driver.go

@@ -45,7 +45,7 @@ func (d *driver) String() string {
 	return "lxc"
 	return "lxc"
 }
 }
 
 
-func (d *driver) Start(c *execdriver.Process) error {
+func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) {
 	params := []string{
 	params := []string{
 		startPath,
 		startPath,
 		"-n", c.ID,
 		"-n", c.ID,
@@ -111,21 +111,32 @@ func (d *driver) Start(c *execdriver.Process) error {
 	c.Args = append([]string{name}, arg...)
 	c.Args = append([]string{name}, arg...)
 
 
 	if err := c.Start(); err != nil {
 	if err := c.Start(); err != nil {
-		return err
+		return -1, err
 	}
 	}
 
 
+	var (
+		waitErr  error
+		waitLock = make(chan struct{})
+	)
 	go func() {
 	go func() {
 		if err := c.Wait(); err != nil {
 		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 {
 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
 	return nil
 }
 }
 
 
-func (d *driver) waitForStart(c *execdriver.Process) error {
+func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) error {
 	var (
 	var (
 		err    error
 		err    error
 		output []byte
 		output []byte
@@ -182,7 +193,7 @@ func (d *driver) waitForStart(c *execdriver.Process) error {
 	// the end of this loop
 	// the end of this loop
 	for now := time.Now(); time.Since(now) < 5*time.Second; {
 	for now := time.Now(); time.Since(now) < 5*time.Second; {
 		select {
 		select {
-		case <-c.WaitLock:
+		case <-waitLock:
 			// If the process dies while waiting for it, just return
 			// If the process dies while waiting for it, just return
 			if c.ProcessState != nil && c.ProcessState.Exited() {
 			if c.ProcessState != nil && c.ProcessState.Exited() {
 				return nil
 				return nil

+ 3 - 3
runtime.go

@@ -192,7 +192,7 @@ func (runtime *Runtime) Register(container *Container) error {
 			}
 			}
 
 
 			container.waitLock = make(chan struct{})
 			container.waitLock = make(chan struct{})
-			go container.monitor()
+			go container.monitor(nil)
 		}
 		}
 	}
 	}
 	return nil
 	return nil
@@ -841,8 +841,8 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) {
 	return archive.ExportChanges(cDir, changes)
 	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 {
 func (runtime *Runtime) Kill(c *Container, sig int) error {