diff options
author | Giuseppe Scrivano <gscrivan@redhat.com> | 2019-05-20 11:56:52 +0200 |
---|---|---|
committer | Giuseppe Scrivano <gscrivan@redhat.com> | 2019-05-20 19:40:15 +0200 |
commit | 57e781462c5f93fcdd37ffd8e2d5ebdb0c6ca27e (patch) | |
tree | 816bd68b8766c4d14cb7bd8eefb9c0a816bfe66e | |
parent | a83edf23ac8fb12f8787ead5257b35425e1996d6 (diff) | |
download | podman-57e781462c5f93fcdd37ffd8e2d5ebdb0c6ca27e.tar.gz podman-57e781462c5f93fcdd37ffd8e2d5ebdb0c6ca27e.tar.bz2 podman-57e781462c5f93fcdd37ffd8e2d5ebdb0c6ca27e.zip |
util: fix race condition in WaitForFile
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: https://github.com/containers/libpod/issues/2942
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
-rw-r--r-- | libpod/util.go | 73 |
1 files changed, 27 insertions, 46 deletions
diff --git a/libpod/util.go b/libpod/util.go index 7e2dff21a..3a15f9e39 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -90,11 +90,7 @@ 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 { @@ -102,51 +98,36 @@ func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, e } 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) } } |