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 9aba820
Showing 1 changed file with 21 additions and 44 deletions.
65 changes: 21 additions & 44 deletions libpod/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,62 +91,39 @@ 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 <-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 {
close(done)
return true, nil
}
case <-inotifyEvents:
_, err := os.Stat(path)
if err == nil {
close(done)
return true, nil
}
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 9aba820

Please sign in to comment.