Skip to content

Commit fd9ec7d

Browse files
zeldovichnathany
authored andcommitted
Properly handle inotify's IN_Q_OVERFLOW event (fsnotify#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
1 parent bd2828f commit fd9ec7d

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

fsnotify.go

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package fsnotify
99

1010
import (
1111
"bytes"
12+
"errors"
1213
"fmt"
1314
)
1415

@@ -60,3 +61,6 @@ func (op Op) String() string {
6061
func (e Event) String() string {
6162
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
6263
}
64+
65+
// Common errors that can be reported by a watcher
66+
var ErrEventOverflow = errors.New("fsnotify queue overflow")

inotify.go

+9
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,15 @@ func (w *Watcher) readEvents() {
245245

246246
mask := uint32(raw.Mask)
247247
nameLen := uint32(raw.Len)
248+
249+
if mask&unix.IN_Q_OVERFLOW != 0 {
250+
select {
251+
case w.Errors <- ErrEventOverflow:
252+
case <-w.done:
253+
return
254+
}
255+
}
256+
248257
// If the event happened to the watched directory or the watched file, the kernel
249258
// doesn't append the filename to the event, but we would like to always fill the
250259
// the "Name" field with a valid filename. We retrieve the path of the watch from

inotify_test.go

+91
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,94 @@ func TestInotifyInnerMapLength(t *testing.T) {
358358
t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
359359
}
360360
}
361+
362+
func TestInotifyOverflow(t *testing.T) {
363+
// We need to generate many more events than the
364+
// fs.inotify.max_queued_events sysctl setting.
365+
// We use multiple goroutines (one per directory)
366+
// to speed up file creation.
367+
numDirs := 128
368+
numFiles := 1024
369+
370+
testDir := tempMkdir(t)
371+
defer os.RemoveAll(testDir)
372+
373+
w, err := NewWatcher()
374+
if err != nil {
375+
t.Fatalf("Failed to create watcher: %v", err)
376+
}
377+
defer w.Close()
378+
379+
for dn := 0; dn < numDirs; dn++ {
380+
testSubdir := fmt.Sprintf("%s/%d", testDir, dn)
381+
382+
err := os.Mkdir(testSubdir, 0777)
383+
if err != nil {
384+
t.Fatalf("Cannot create subdir: %v", err)
385+
}
386+
387+
err = w.Add(testSubdir)
388+
if err != nil {
389+
t.Fatalf("Failed to add subdir: %v", err)
390+
}
391+
}
392+
393+
errChan := make(chan error, numDirs*numFiles)
394+
395+
for dn := 0; dn < numDirs; dn++ {
396+
testSubdir := fmt.Sprintf("%s/%d", testDir, dn)
397+
398+
go func() {
399+
for fn := 0; fn < numFiles; fn++ {
400+
testFile := fmt.Sprintf("%s/%d", testSubdir, fn)
401+
402+
handle, err := os.Create(testFile)
403+
if err != nil {
404+
errChan <- fmt.Errorf("Create failed: %v", err)
405+
continue
406+
}
407+
408+
err = handle.Close()
409+
if err != nil {
410+
errChan <- fmt.Errorf("Close failed: %v", err)
411+
continue
412+
}
413+
}
414+
}()
415+
}
416+
417+
creates := 0
418+
overflows := 0
419+
420+
after := time.After(10 * time.Second)
421+
for overflows == 0 && creates < numDirs*numFiles {
422+
select {
423+
case <-after:
424+
t.Fatalf("Not done")
425+
case err := <-errChan:
426+
t.Fatalf("Got an error from file creator goroutine: %v", err)
427+
case err := <-w.Errors:
428+
if err == ErrEventOverflow {
429+
overflows++
430+
} else {
431+
t.Fatalf("Got an error from watcher: %v", err)
432+
}
433+
case evt := <-w.Events:
434+
if !strings.HasPrefix(evt.Name, testDir) {
435+
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
436+
}
437+
if evt.Op == Create {
438+
creates++
439+
}
440+
}
441+
}
442+
443+
if creates == numDirs*numFiles {
444+
t.Fatalf("Could not trigger overflow")
445+
}
446+
447+
if overflows == 0 {
448+
t.Fatalf("No overflow and not enough creates (expected %d, got %d)",
449+
numDirs*numFiles, creates)
450+
}
451+
}

0 commit comments

Comments
 (0)