Skip to content

Commit 50f4429

Browse files
committed
gracefully handle too many open files instead of crashing
When we hit "too many open files" (syscall.EMFILE), unwatch the last 256 files (or 20% of the currently watched files, whichever is smaller) so that the commands after that may use their own file descriptors and not have all file descriptors sucked up by the watcher. In the future we may decide to gracefully fallback to polling, but this error really only happens when somebody does something crazy like watching their entire home directory and I'm not sure I want to empower that.
1 parent 10693cd commit 50f4429

File tree

1 file changed

+41
-4
lines changed

1 file changed

+41
-4
lines changed

wgo_cmd.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"strconv"
1818
"strings"
1919
"sync"
20+
"syscall"
2021
"time"
2122
"unicode/utf8"
2223

@@ -408,8 +409,8 @@ func (wgoCmd *WgoCmd) Run() error {
408409
defer watcher.Close()
409410
for _, root := range wgoCmd.Roots {
410411
if wgoCmd.PollDuration > 0 {
411-
wgoCmd.Logger.Println("POLL", filepath.ToSlash(root))
412412
go wgoCmd.pollDirectory(wgoCmd.ctx, root, events)
413+
wgoCmd.Logger.Println("POLL", filepath.ToSlash(root))
413414
} else {
414415
wgoCmd.addDirsRecursively(watcher, root)
415416
}
@@ -629,8 +630,14 @@ func (wgoCmd *WgoCmd) addDirsRecursively(watcher *fsnotify.Watcher, dir string)
629630
normalizedDir := filepath.ToSlash(path)
630631
_, isRoot := roots[path]
631632
if isRoot {
633+
err := watcher.Add(path)
634+
if err != nil {
635+
if errors.Is(err, syscall.EMFILE) {
636+
return fs.SkipAll
637+
}
638+
return fs.SkipDir
639+
}
632640
wgoCmd.Logger.Println("WATCH", normalizedDir)
633-
watcher.Add(path)
634641
return nil
635642
}
636643
for _, root := range wgoCmd.Roots {
@@ -646,8 +653,23 @@ func (wgoCmd *WgoCmd) addDirsRecursively(watcher *fsnotify.Watcher, dir string)
646653
}
647654
for _, r := range wgoCmd.DirRegexps {
648655
if r.MatchString(normalizedDir) {
656+
err := watcher.Add(path)
657+
if err != nil {
658+
if errors.Is(err, syscall.EMFILE) {
659+
watchList := watcher.WatchList()
660+
unwatchCount := 256
661+
if unwatchCount > len(watchList)/2 {
662+
unwatchCount = int(0.2 * float64(len(watchList)))
663+
}
664+
for i := len(watchList) - 1; i >= unwatchCount; i-- {
665+
watcher.Remove(watchList[i])
666+
}
667+
wgoCmd.Logger.Printf("ERROR too many open files (%d open files), not watching any more\n", len(watchList))
668+
return fs.SkipAll
669+
}
670+
return fs.SkipDir
671+
}
649672
wgoCmd.Logger.Println("WATCH", normalizedDir)
650-
watcher.Add(path)
651673
return nil
652674
}
653675
}
@@ -659,8 +681,23 @@ func (wgoCmd *WgoCmd) addDirsRecursively(watcher *fsnotify.Watcher, dir string)
659681
if strings.HasPrefix(name, ".") {
660682
return filepath.SkipDir
661683
}
684+
err = watcher.Add(path)
685+
if err != nil {
686+
if errors.Is(err, syscall.EMFILE) {
687+
watchList := watcher.WatchList()
688+
unwatchCount := 256
689+
if unwatchCount > len(watchList)/2 {
690+
unwatchCount = int(0.2 * float64(len(watchList)))
691+
}
692+
for i := len(watchList) - 1; i >= unwatchCount; i-- {
693+
watcher.Remove(watchList[i])
694+
}
695+
wgoCmd.Logger.Printf("ERROR too many open files (%d open files), not watching any more\n", len(watchList))
696+
return fs.SkipAll
697+
}
698+
return fs.SkipDir
699+
}
662700
wgoCmd.Logger.Println("WATCH", normalizedDir)
663-
watcher.Add(path)
664701
return nil
665702
})
666703
}

0 commit comments

Comments
 (0)