Skip to content

Commit

Permalink
Update documentation and examples
Browse files Browse the repository at this point in the history
Update some documentation and add various examples. I added the examples
as subcommands of ./cmd/fsnotify, so they're directly runnable, and it's
at least guaranteed they compile.

This adds some simpler test cases so it's easier to verify it actually
works as documented on all platforms, and it adds internal.Debug() for
all platforms, which is useful in dev.

Fixes 49
Fixes 74
Fixes 94
Fixes 122
Fixes 238
Fixes 372
Fixes 401
  • Loading branch information
arp242 committed Aug 9, 2022
1 parent e180a87 commit 9c96789
Show file tree
Hide file tree
Showing 26 changed files with 1,645 additions and 141 deletions.
87 changes: 45 additions & 42 deletions README.md
Expand Up @@ -28,52 +28,55 @@ A basic example:
package main

import (
"log"
"log"

"github.com/fsnotify/fsnotify"
"github.com/fsnotify/fsnotify"
)

func main() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()

done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Has(fsnotify.Write) {
log.Println("modified file:", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()

err = watcher.Add("/tmp")
if err != nil {
log.Fatal(err)
}
<-done
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()

// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Has(fsnotify.Write) {
log.Println("modified file:", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()

// Add a path.
err = watcher.Add("/tmp")
if err != nil {
log.Fatal(err)
}

// Block main goroutine forever.
<-make(chan struct{})
}
```

A slightly more expansive example can be found in [cmd/fsnotify](cmd/fsnotify),
which can be run with:
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
run with:

# Watch the current directory (not recursive).
$ go run ./cmd/fsnotify .
% go run ./cmd/fsnotify

FAQ
---
Expand Down Expand Up @@ -109,10 +112,10 @@ descriptors are closed. It will emit a CHMOD though:
os.Remove("file") // CHMOD
fp.Close() // REMOVE

Linux: the `fs.inotify.max_user_watches` sysctl variable specifies the upper
limit for the number of watches per user, and `fs.inotify.max_user_instances`
specifies the maximum number of inotify instances per user. Every Watcher you
create is an "instance", and every path you add is a "watch".
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
the number of watches per user, and `fs.inotify.max_user_instances` specifies
the maximum number of inotify instances per user. Every Watcher you create is an
"instance", and every path you add is a "watch".

These are also exposed in /proc as `/proc/sys/fs/inotify/max_user_watches` and
`/proc/sys/fs/inotify/max_user_instances`
Expand Down
127 changes: 124 additions & 3 deletions backend_fen.go
Expand Up @@ -8,12 +8,99 @@ import (
)

// Watcher watches a set of files, delivering events to a channel.
//
// A watcher should not be copied (e.g. pass it by pointer, rather than by
// value).
//
// # Events
//
// fsnotify can send the following events; a "path" here can refer to a file,
// directory, symbolic link, or special files like a FIFO.
//
// fsnotify.Create A new path was created; this may be followed by one or
// more Write events if data also gets written to a file.
//
// fsnotify.Remove A path was removed.
//
// fsnotify.Rename A path was renamed. A rename is always sent with the old
// path as [Event.Name], and a Create event will be sent
// with the new name. Renames are only sent for paths that
// are currently watched; e.g. moving an unmonitored file
// into a monitored directory will show up as just a
// Create. Similarly, renaming a file to outside a
// monitored directory will show up as only a Rename.
//
// fsnotify.Write A file or named pipe was written to. A Truncate will
// also trigger a Write. A single "write action" initiated
// by the user may show up as one or multiple writes,
// depending on when the system syncs things to disk. For
// example when compiling a large Go program you may get
// hundreds of Write events, so you probably want to wait
// until you've stopped receiving them (see the dedup
// example in cmd/fsnotify).
//
// fsnotify.Chmod Attributes were changes (never sent on Windows). On
// Linux this is also sent when a file is removed (or more
// accurately, when a link to an inode is removed), and on
// kqueue when a file is truncated.
//
// # Linux notes
//
// When a file is removed a Remove event won't be emitted until all file
// descriptors are closed, and deletes will always emit a Chmod. For example:
//
// fp := os.Open("file")
// os.Remove("file") // Triggers Chmod
// fp.Close() // Triggers Remove
//
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
// for the number of watches per user, and fs.inotify.max_user_instances
// specifies the maximum number of inotify instances per user. Every Watcher you
// create is an "instance", and every path you add is a "watch".
//
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
// /proc/sys/fs/inotify/max_user_instances
//
// To increase them you can use sysctl or write the value to the /proc file:
//
// # Default values on Linux 5.18
// sysctl fs.inotify.max_user_watches=124983
// sysctl fs.inotify.max_user_instances=128
//
// To make the changes persist on reboot edit /etc/sysctl.conf or
// /usr/lib/sysctl.d/50-default.conf (on some systemd systems):
//
// fs.inotify.max_user_watches=124983
// fs.inotify.max_user_instances=128
//
// Reaching the limit will result in a "no space left on device" or "too many open
// files" error.
//
// # kqueue notes (macOS, BSD)
//
// kqueue requires opening a file descriptor for every file that's being watched;
// so if you're watching a directory with five files then that's six file
// descriptors. You will run in to your system's "max open files" limit faster on
// these platforms.
//
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
// control the maximum number of open files, as well as /etc/login.conf on BSD
// systems.
//
// # macOS notes
//
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
// Settings" until we have a native FSEvents implementation (see [#11]).
//
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
type Watcher struct {
Events chan Event
Errors chan error
}

// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
// NewWatcher creates a new Watcher.
func NewWatcher() (*Watcher, error) {
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
}
Expand All @@ -23,12 +110,46 @@ func (w *Watcher) Close() error {
return nil
}

// Add starts watching the named file or directory (non-recursively).
// Add starts monitoring the path for changes.
//
// A path can only be watched once; attempting to watch it more than once will
// return an error. Paths that do not yet exist on the filesystem cannot be
// added. A watch will be automatically removed if the path is deleted.
//
// A path will remain watched if it gets renamed to somewhere else on the same
// filesystem, but the monitor will get removed if the path gets deleted and
// re-created.
//
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
// filesystems (/proc, /sys, etc.) generally don't work.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
// after the watcher is started. Subdirectories are not watched (i.e. it's
// non-recursive).
//
// # Watching files
//
// Watching individual files (rather than directories) is generally not
// recommended as many tools update files atomically. Instead of "just" writing
// to the file a temporary file will be written to first, and if successful the
// temporary file is moved to to destination, removing the original, or some
// variant thereof. The watcher on the original file is now lost, as it no
// longer exists.
//
// Instead, watch the parent directory and use [Event.Name] to filter out files
// you're not interested in. There is an example of this in cmd/fsnotify/file.go
func (w *Watcher) Add(name string) error {
return nil
}

// Remove stops watching the the named file or directory (non-recursively).
// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
//
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
func (w *Watcher) Remove(name string) error {
return nil
}

0 comments on commit 9c96789

Please sign in to comment.