summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiuseppe Scrivano <gscrivan@redhat.com>2019-05-20 11:56:52 +0200
committerGiuseppe Scrivano <gscrivan@redhat.com>2019-05-20 19:40:15 +0200
commit57e781462c5f93fcdd37ffd8e2d5ebdb0c6ca27e (patch)
tree816bd68b8766c4d14cb7bd8eefb9c0a816bfe66e
parenta83edf23ac8fb12f8787ead5257b35425e1996d6 (diff)
downloadpodman-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.go73
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)
}
}