Skip to content

Resilience

When the inotify watch limit is reached (ENOSPC), go-filewatcher does not crash. Instead:

  • The error is logged via the error handler
  • The WatchErrors counter is incremented
  • Walking continues to add remaining directories
  • The watcher starts in degraded mode
watcher, _ := filewatcher.New(paths)
stats := watcher.Stats()
fmt.Printf("Watch errors: %d\n", stats.WatchErrors)

go-filewatcher auto-detects the inotify watch budget from /proc/sys/fs/inotify/max_user_watches on Linux. Override with WithMaxWatches:

watcher, _ := filewatcher.New(
[]string{"./large-monorepo"},
filewatcher.WithMaxWatches(524288), // Override budget
)
stats := watcher.Stats()
fmt.Printf("watching %d/%d paths (%.1f%% budget)\n",
stats.WatchCount, stats.WatchLimit,
stats.WatchBudgetUsed*100)

When the budget is exhausted, directories are skipped silently and counted in Stats.WatchErrors.

WithSelfHeal automatically retries failed watch registrations at the given interval:

watcher, _ := filewatcher.New(
[]string{"./src"},
filewatcher.WithSelfHeal(30 * time.Second), // Retry every 30s
)

This handles transient failures like temporary ENOSPC or mount points that become available later.

For environments where OS-native inotify events don’t work (NFS, FUSE, Docker volumes), enable polling mode:

watcher, _ := filewatcher.New(
[]string{"/mnt/nfs/share"},
filewatcher.WithPolling(true),
filewatcher.WithPollInterval(2 * time.Second),
)

Polling supplements fsnotify events. The poll loop maintains a filesystem snapshot and detects new, modified, and removed files at each interval.

Enabled by default (WithGitignore(true)). Directories matching .gitignore patterns are skipped during the walk — they’re never added to inotify:

// Default: enabled
watcher, _ := filewatcher.New(paths)
// Explicitly disable
watcher, _ := filewatcher.New(paths,
filewatcher.WithGitignore(false),
)

Exclude entire subtrees by absolute path during the walk:

watcher, _ := filewatcher.New(
[]string{"/home/me/projects"},
filewatcher.WithExcludePaths(
"/home/me/projects/forks", // Skip forks entirely
"/home/me/projects/archived", // Skip archived projects
),
)

Uses prefix matching: excluding /home/me/forks skips all subdirectories too.

Follow symbolic links during directory walking:

watcher, _ := filewatcher.New(paths,
filewatcher.WithFollowSymlinks(true), // Default: false
)

Directories are collected during the walk and added in batches of 1000, with runtime.Gosched() between batches. This reduces startup latency for large directory trees and yields to event processing.