From fd9ec7deca8bf46ecd2a795baaacf2b3a9be1197 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Wed, 26 Oct 2016 16:31:22 -0400 Subject: [PATCH] Properly handle inotify's IN_Q_OVERFLOW event (#149) * Properly handle inotify's IN_Q_OVERFLOW event Upon receiving an event with IN_Q_OVERFLOW set in the mask, generate an error on the Errors chan, so that the application can take appropriate action. * Use a well-defined error (ErrEventOverflow) for inotify overflow * Add a test for inotify queue overflow --- fsnotify.go | 4 +++ inotify.go | 9 +++++ inotify_test.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/fsnotify.go b/fsnotify.go index e7f55fe..190bf0d 100644 --- a/fsnotify.go +++ b/fsnotify.go @@ -9,6 +9,7 @@ package fsnotify import ( "bytes" + "errors" "fmt" ) @@ -60,3 +61,6 @@ func (op Op) String() string { func (e Event) String() string { return fmt.Sprintf("%q: %s", e.Name, e.Op.String()) } + +// Common errors that can be reported by a watcher +var ErrEventOverflow = errors.New("fsnotify queue overflow") diff --git a/inotify.go b/inotify.go index f3b74c5..bfa9dbc 100644 --- a/inotify.go +++ b/inotify.go @@ -245,6 +245,15 @@ func (w *Watcher) readEvents() { mask := uint32(raw.Mask) nameLen := uint32(raw.Len) + + if mask&unix.IN_Q_OVERFLOW != 0 { + select { + case w.Errors <- ErrEventOverflow: + case <-w.done: + return + } + } + // If the event happened to the watched directory or the watched file, the kernel // doesn't append the filename to the event, but we would like to always fill the // the "Name" field with a valid filename. We retrieve the path of the watch from diff --git a/inotify_test.go b/inotify_test.go index a4bb202..6771a1c 100644 --- a/inotify_test.go +++ b/inotify_test.go @@ -358,3 +358,94 @@ func TestInotifyInnerMapLength(t *testing.T) { t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths) } } + +func TestInotifyOverflow(t *testing.T) { + // We need to generate many more events than the + // fs.inotify.max_queued_events sysctl setting. + // We use multiple goroutines (one per directory) + // to speed up file creation. + numDirs := 128 + numFiles := 1024 + + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + for dn := 0; dn < numDirs; dn++ { + testSubdir := fmt.Sprintf("%s/%d", testDir, dn) + + err := os.Mkdir(testSubdir, 0777) + if err != nil { + t.Fatalf("Cannot create subdir: %v", err) + } + + err = w.Add(testSubdir) + if err != nil { + t.Fatalf("Failed to add subdir: %v", err) + } + } + + errChan := make(chan error, numDirs*numFiles) + + for dn := 0; dn < numDirs; dn++ { + testSubdir := fmt.Sprintf("%s/%d", testDir, dn) + + go func() { + for fn := 0; fn < numFiles; fn++ { + testFile := fmt.Sprintf("%s/%d", testSubdir, fn) + + handle, err := os.Create(testFile) + if err != nil { + errChan <- fmt.Errorf("Create failed: %v", err) + continue + } + + err = handle.Close() + if err != nil { + errChan <- fmt.Errorf("Close failed: %v", err) + continue + } + } + }() + } + + creates := 0 + overflows := 0 + + after := time.After(10 * time.Second) + for overflows == 0 && creates < numDirs*numFiles { + select { + case <-after: + t.Fatalf("Not done") + case err := <-errChan: + t.Fatalf("Got an error from file creator goroutine: %v", err) + case err := <-w.Errors: + if err == ErrEventOverflow { + overflows++ + } else { + t.Fatalf("Got an error from watcher: %v", err) + } + case evt := <-w.Events: + if !strings.HasPrefix(evt.Name, testDir) { + t.Fatalf("Got an event for an unknown file: %s", evt.Name) + } + if evt.Op == Create { + creates++ + } + } + } + + if creates == numDirs*numFiles { + t.Fatalf("Could not trigger overflow") + } + + if overflows == 0 { + t.Fatalf("No overflow and not enough creates (expected %d, got %d)", + numDirs*numFiles, creates) + } +} -- 2.50.1