Merge pull request #1833 from giuseppe/remove-exec-polling

exec: remove polling for PID file
This commit is contained in:
OpenShift Merge Robot
2018-11-28 05:10:49 -08:00
committed by GitHub
2 changed files with 52 additions and 16 deletions

View File

@ -328,6 +328,11 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
if err != nil {
return errors.Wrapf(err, "error exec %s", c.ID())
}
chWait := make(chan error)
go func() {
chWait <- execCmd.Wait()
}()
defer close(chWait)
pidFile := c.execPidPath(sessionID)
// 60 second seems a reasonable time to wait
@ -336,18 +341,12 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
const pidWaitTimeout = 60000
// Wait until the runtime makes the pidfile
// TODO: If runtime errors before the PID file is created, we have to
// wait for timeout here
if err := WaitForFile(pidFile, pidWaitTimeout*time.Millisecond); err != nil {
logrus.Debugf("Timed out waiting for pidfile from runtime for container %s exec", c.ID())
// Check if an error occurred in the process before we made a pidfile
// TODO: Wait() here is a poor choice - is there a way to see if
// a process has finished, instead of waiting for it to finish?
if err := execCmd.Wait(); err != nil {
exited, err := WaitForFile(pidFile, chWait, pidWaitTimeout*time.Millisecond)
if err != nil {
if exited {
// If the runtime exited, propagate the error we got from the process.
return err
}
return errors.Wrapf(err, "timed out waiting for runtime to create pidfile for exec session in container %s", c.ID())
}
@ -389,7 +388,10 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
locked = false
}
waitErr := execCmd.Wait()
var waitErr error
if !exited {
waitErr = <-chWait
}
// Lock again
if !c.batched {

View File

@ -13,6 +13,7 @@ import (
"github.com/containers/image/signature"
"github.com/containers/image/types"
"github.com/containers/libpod/pkg/util"
"github.com/fsnotify/fsnotify"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@ -90,31 +91,64 @@ func MountExists(specMounts []spec.Mount, dest string) bool {
}
// WaitForFile waits until a file has been created or the given timeout has occurred
func WaitForFile(path string, timeout time.Duration) error {
func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, error) {
done := make(chan struct{})
chControl := make(chan struct{})
var inotifyEvents chan fsnotify.Event
var timer chan struct{}
watcher, err := fsnotify.NewWatcher()
if err == nil {
if err := watcher.Add(filepath.Dir(path)); err == nil {
inotifyEvents = watcher.Events
}
defer watcher.Close()
}
if inotifyEvents == nil {
// If for any reason we fail to create the inotify
// watcher, fallback to polling the file
timer = make(chan struct{})
go func() {
select {
case <-chControl:
close(timer)
return
default:
time.Sleep(25 * time.Millisecond)
timer <- struct{}{}
}
}()
}
go func() {
for {
select {
case <-chControl:
return
default:
case <-timer:
_, err := os.Stat(path)
if err == nil {
close(done)
return
}
case <-inotifyEvents:
_, err := os.Stat(path)
if err == nil {
close(done)
return
}
time.Sleep(25 * time.Millisecond)
}
}
}()
select {
case e := <-chWait:
return true, e
case <-done:
return nil
return false, nil
case <-time.After(timeout):
close(chControl)
return errors.Wrapf(ErrInternal, "timed out waiting for file %s", path)
return false, errors.Wrapf(ErrInternal, "timed out waiting for file %s", path)
}
}