Skip to content

Commit 61a05ce

Browse files
authored
Update documentation and examples (#496)
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
1 parent e180a87 commit 61a05ce

26 files changed

+1675
-147
lines changed

README.md

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,52 +28,55 @@ A basic example:
2828
package main
2929

3030
import (
31-
"log"
31+
"log"
3232

33-
"github.com/fsnotify/fsnotify"
33+
"github.com/fsnotify/fsnotify"
3434
)
3535

3636
func main() {
37-
watcher, err := fsnotify.NewWatcher()
38-
if err != nil {
39-
log.Fatal(err)
40-
}
41-
defer watcher.Close()
42-
43-
done := make(chan bool)
44-
go func() {
45-
for {
46-
select {
47-
case event, ok := <-watcher.Events:
48-
if !ok {
49-
return
50-
}
51-
log.Println("event:", event)
52-
if event.Has(fsnotify.Write) {
53-
log.Println("modified file:", event.Name)
54-
}
55-
case err, ok := <-watcher.Errors:
56-
if !ok {
57-
return
58-
}
59-
log.Println("error:", err)
60-
}
61-
}
62-
}()
63-
64-
err = watcher.Add("/tmp")
65-
if err != nil {
66-
log.Fatal(err)
67-
}
68-
<-done
37+
// Create new watcher.
38+
watcher, err := fsnotify.NewWatcher()
39+
if err != nil {
40+
log.Fatal(err)
41+
}
42+
defer watcher.Close()
43+
44+
// Start listening for events.
45+
go func() {
46+
for {
47+
select {
48+
case event, ok := <-watcher.Events:
49+
if !ok {
50+
return
51+
}
52+
log.Println("event:", event)
53+
if event.Has(fsnotify.Write) {
54+
log.Println("modified file:", event.Name)
55+
}
56+
case err, ok := <-watcher.Errors:
57+
if !ok {
58+
return
59+
}
60+
log.Println("error:", err)
61+
}
62+
}
63+
}()
64+
65+
// Add a path.
66+
err = watcher.Add("/tmp")
67+
if err != nil {
68+
log.Fatal(err)
69+
}
70+
71+
// Block main goroutine forever.
72+
<-make(chan struct{})
6973
}
7074
```
7175

72-
A slightly more expansive example can be found in [cmd/fsnotify](cmd/fsnotify),
73-
which can be run with:
76+
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
77+
run with:
7478

75-
# Watch the current directory (not recursive).
76-
$ go run ./cmd/fsnotify .
79+
% go run ./cmd/fsnotify
7780

7881
FAQ
7982
---
@@ -109,10 +112,10 @@ descriptors are closed. It will emit a CHMOD though:
109112
os.Remove("file") // CHMOD
110113
fp.Close() // REMOVE
111114

112-
Linux: the `fs.inotify.max_user_watches` sysctl variable specifies the upper
113-
limit for the number of watches per user, and `fs.inotify.max_user_instances`
114-
specifies the maximum number of inotify instances per user. Every Watcher you
115-
create is an "instance", and every path you add is a "watch".
115+
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
116+
the number of watches per user, and `fs.inotify.max_user_instances` specifies
117+
the maximum number of inotify instances per user. Every Watcher you create is an
118+
"instance", and every path you add is a "watch".
116119

117120
These are also exposed in /proc as `/proc/sys/fs/inotify/max_user_watches` and
118121
`/proc/sys/fs/inotify/max_user_instances`

backend_fen.go

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,102 @@ import (
88
)
99

1010
// Watcher watches a set of files, delivering events to a channel.
11+
//
12+
// A watcher should not be copied (e.g. pass it by pointer, rather than by
13+
// value).
14+
//
15+
// # Linux notes
16+
//
17+
// When a file is removed a Remove event won't be emitted until all file
18+
// descriptors are closed, and deletes will always emit a Chmod. For example:
19+
//
20+
// fp := os.Open("file")
21+
// os.Remove("file") // Triggers Chmod
22+
// fp.Close() // Triggers Remove
23+
//
24+
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
25+
// for the number of watches per user, and fs.inotify.max_user_instances
26+
// specifies the maximum number of inotify instances per user. Every Watcher you
27+
// create is an "instance", and every path you add is a "watch".
28+
//
29+
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
30+
// /proc/sys/fs/inotify/max_user_instances
31+
//
32+
// To increase them you can use sysctl or write the value to the /proc file:
33+
//
34+
// # Default values on Linux 5.18
35+
// sysctl fs.inotify.max_user_watches=124983
36+
// sysctl fs.inotify.max_user_instances=128
37+
//
38+
// To make the changes persist on reboot edit /etc/sysctl.conf or
39+
// /usr/lib/sysctl.d/50-default.conf (on some systemd systems):
40+
//
41+
// fs.inotify.max_user_watches=124983
42+
// fs.inotify.max_user_instances=128
43+
//
44+
// Reaching the limit will result in a "no space left on device" or "too many open
45+
// files" error.
46+
//
47+
// # kqueue notes (macOS, BSD)
48+
//
49+
// kqueue requires opening a file descriptor for every file that's being watched;
50+
// so if you're watching a directory with five files then that's six file
51+
// descriptors. You will run in to your system's "max open files" limit faster on
52+
// these platforms.
53+
//
54+
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
55+
// control the maximum number of open files, as well as /etc/login.conf on BSD
56+
// systems.
57+
//
58+
// # macOS notes
59+
//
60+
// Spotlight indexing on macOS can result in multiple events (see [#15]). A
61+
// temporary workaround is to add your folder(s) to the "Spotlight Privacy
62+
// Settings" until we have a native FSEvents implementation (see [#11]).
63+
//
64+
// [#11]: https://github.com/fsnotify/fsnotify/issues/11
65+
// [#15]: https://github.com/fsnotify/fsnotify/issues/15
1166
type Watcher struct {
67+
// Events sends the filesystem change events.
68+
//
69+
// fsnotify can send the following events; a "path" here can refer to a
70+
// file, directory, symbolic link, or special files like a FIFO.
71+
//
72+
// fsnotify.Create A new path was created; this may be followed by one
73+
// or more Write events if data also gets written to a
74+
// file.
75+
//
76+
// fsnotify.Remove A path was removed.
77+
//
78+
// fsnotify.Rename A path was renamed. A rename is always sent with the
79+
// old path as [Event.Name], and a Create event will be
80+
// sent with the new name. Renames are only sent for
81+
// paths that are currently watched; e.g. moving an
82+
// unmonitored file into a monitored directory will
83+
// show up as just a Create. Similarly, renaming a file
84+
// to outside a monitored directory will show up as
85+
// only a Rename.
86+
//
87+
// fsnotify.Write A file or named pipe was written to. A Truncate will
88+
// also trigger a Write. A single "write action"
89+
// initiated by the user may show up as one or multiple
90+
// writes, depending on when the system syncs things to
91+
// disk. For example when compiling a large Go program
92+
// you may get hundreds of Write events, so you
93+
// probably want to wait until you've stopped receiving
94+
// them (see the dedup example in cmd/fsnotify).
95+
//
96+
// fsnotify.Chmod Attributes were changes (never sent on Windows). On
97+
// Linux this is also sent when a file is removed (or
98+
// more accurately, when a link to an inode is
99+
// removed), and on kqueue when a file is truncated.
12100
Events chan Event
101+
102+
// Errors sends any errors.
13103
Errors chan error
14104
}
15105

16-
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
106+
// NewWatcher creates a new Watcher.
17107
func NewWatcher() (*Watcher, error) {
18108
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
19109
}
@@ -23,12 +113,46 @@ func (w *Watcher) Close() error {
23113
return nil
24114
}
25115

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

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

0 commit comments

Comments
 (0)