Skip to content

Commit

Permalink
util: fix race condition in WaitForFile
Browse files Browse the repository at this point in the history
enable polling also when using inotify.  It is generally useful to
have it as under high load inotify can lose notifications.  It also
solves a race condition where the file is created while the watcher
is configured and it'd wait until the timeout and fail.

Closes: containers#2942

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
  • Loading branch information
giuseppe committed May 20, 2019
1 parent a83edf2 commit 390f29c
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 47 deletions.
2 changes: 1 addition & 1 deletion libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
chWait := make(chan error)
go func() {
chWait <- execCmd.Wait()
close(chWait)
}()
defer close(chWait)

pidFile := c.execPidPath(sessionID)
// 60 second seems a reasonable time to wait
Expand Down
73 changes: 27 additions & 46 deletions libpod/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,63 +90,44 @@ 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, 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
case <-timer:
_, err := os.Stat(path)
if err == nil {
close(done)
return
}
case <-inotifyEvents:
_, err := os.Stat(path)
if err == nil {
close(done)
return
}
timeoutChan := time.After(timeout)

for {
select {
case e := <-chWait:
return true, e
case <-inotifyEvents:
_, err := os.Stat(path)
if err == nil {
return false, nil
}
if !os.IsNotExist(err) {
return false, errors.Wrapf(err, "checking file %s", path)
}
case <-time.After(25 * time.Millisecond):
// Check periodically for the file existence. It is needed
// if the inotify watcher could not have been created. It is
// also useful when using inotify as if for any reasons we missed
// a notification, we won't hang the process.
_, err := os.Stat(path)
if err == nil {
return false, nil
}
if !os.IsNotExist(err) {
return false, errors.Wrapf(err, "checking file %s", path)
}
case <-timeoutChan:
return false, errors.Wrapf(ErrInternal, "timed out waiting for file %s", path)
}
}()

select {
case e := <-chWait:
return true, e
case <-done:
return false, nil
case <-time.After(timeout):
close(chControl)
return false, errors.Wrapf(ErrInternal, "timed out waiting for file %s", path)
}
}

Expand Down

0 comments on commit 390f29c

Please sign in to comment.