Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AddWith() to pass options, allow controlling Windows buffer size #521

Merged
merged 1 commit into from Oct 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 15 additions & 2 deletions backend_fen.go
Expand Up @@ -105,7 +105,7 @@ type Watcher struct {
// [ErrEventOverflow] is used to indicate there are too many events:
//
// - inotify: there are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small.
// - windows: The buffer size is too small; [WithBufferSize] can be used to increase it.
// - kqueue, fen: not used.
Errors chan error

Expand Down Expand Up @@ -195,6 +195,8 @@ func (w *Watcher) Close() error {
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
Expand All @@ -212,14 +214,25 @@ func (w *Watcher) Close() error {
//
// 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 {
func (w *Watcher) Add(name string) error { return w.AddWith(name) }

// AddWith is like [Add], but allows adding options. When using Add() the
// defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
if w.isClosed() {
return ErrClosed
}
if w.port.PathIsWatched(name) {
return nil
}

_ = getOptions(opts...)

// Currently we resolve symlinks that were explicitly requested to be
// watched. Otherwise we would use LStat here.
stat, err := os.Stat(name)
Expand Down
19 changes: 16 additions & 3 deletions backend_inotify.go
Expand Up @@ -108,7 +108,7 @@ type Watcher struct {
// [ErrEventOverflow] is used to indicate there are too many events:
//
// - inotify: there are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small.
// - windows: The buffer size is too small; [WithBufferSize] can be used to increase it.
// - kqueue, fen: not used.
Errors chan error

Expand Down Expand Up @@ -217,6 +217,8 @@ func (w *Watcher) Close() error {
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
Expand All @@ -234,12 +236,23 @@ func (w *Watcher) Close() error {
//
// 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 {
name = filepath.Clean(name)
func (w *Watcher) Add(name string) error { return w.AddWith(name) }

// AddWith is like [Add], but allows adding options. When using Add() the
// defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
if w.isClosed() {
return ErrClosed
}

name = filepath.Clean(name)
_ = getOptions(opts...)

var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
Expand Down
17 changes: 15 additions & 2 deletions backend_kqueue.go
Expand Up @@ -106,7 +106,7 @@ type Watcher struct {
// [ErrEventOverflow] is used to indicate there are too many events:
//
// - inotify: there are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small.
// - windows: The buffer size is too small; [WithBufferSize] can be used to increase it.
// - kqueue, fen: not used.
Errors chan error

Expand Down Expand Up @@ -249,6 +249,8 @@ func (w *Watcher) Close() error {
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
Expand All @@ -266,7 +268,18 @@ func (w *Watcher) Close() error {
//
// 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 {
func (w *Watcher) Add(name string) error { return w.AddWith(name) }

// AddWith is like [Add], but allows adding options. When using Add() the
// defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
_ = getOptions(opts...)

w.mu.Lock()
w.userWatches[name] = struct{}{}
w.mu.Unlock()
Expand Down
15 changes: 14 additions & 1 deletion backend_other.go
Expand Up @@ -100,7 +100,7 @@ type Watcher struct {
// [ErrEventOverflow] is used to indicate there are too many events:
//
// - inotify: there are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small.
// - windows: The buffer size is too small; [WithBufferSize] can be used to increase it.
// - kqueue, fen: not used.
Errors chan error
}
Expand Down Expand Up @@ -133,6 +133,8 @@ func (w *Watcher) WatchList() []string { return nil }
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
Expand All @@ -152,6 +154,17 @@ func (w *Watcher) WatchList() []string { return nil }
// 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 }

// AddWith is like [Add], but allows adding options. When using Add() the
// defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
return nil
}

// Remove stops monitoring the path for changes.
//
// Directories are always removed non-recursively. For example, if you added
Expand Down
52 changes: 37 additions & 15 deletions backend_windows.go
Expand Up @@ -109,7 +109,7 @@ type Watcher struct {
// [ErrEventOverflow] is used to indicate there are too many events:
//
// - inotify: there are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small.
// - windows: The buffer size is too small; [WithBufferSize] can be used to increase it.
// - kqueue, fen: not used.
Errors chan error

Expand Down Expand Up @@ -204,6 +204,8 @@ func (w *Watcher) Close() error {
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
Expand All @@ -221,16 +223,31 @@ func (w *Watcher) Close() error {
//
// 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 {
func (w *Watcher) Add(name string) error { return w.AddWith(name) }

// AddWith is like [Add], but allows adding options. When using Add() the
// defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
if w.isClosed() {
return ErrClosed
}

with := getOptions(opts...)
if with.bufsize < 4096 {
return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
}

in := &input{
op: opAddWatch,
path: filepath.Clean(name),
flags: sysFSALLEVENTS,
reply: make(chan error),
op: opAddWatch,
path: filepath.Clean(name),
flags: sysFSALLEVENTS,
reply: make(chan error),
bufsize: with.bufsize,
}
w.input <- in
if err := w.wakeupReader(); err != nil {
Expand Down Expand Up @@ -329,10 +346,11 @@ const (
)

type input struct {
op int
path string
flags uint32
reply chan error
op int
path string
flags uint32
bufsize int
reply chan error
}

type inode struct {
Expand All @@ -348,7 +366,7 @@ type watch struct {
mask uint64 // Directory itself is being watched with these notify flags
names map[string]uint64 // Map of names being watched and their notify flags
rename string // Remembers the old name while renaming a file
buf [65536]byte // 64K buffer
buf []byte // buffer, allocated later
}

type (
Expand Down Expand Up @@ -421,7 +439,7 @@ func (m watchMap) set(ino *inode, watch *watch) {
}

// Must run within the I/O thread.
func (w *Watcher) addWatch(pathname string, flags uint64) error {
func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
dir, err := w.getDir(pathname)
if err != nil {
return err
Expand All @@ -444,6 +462,7 @@ func (w *Watcher) addWatch(pathname string, flags uint64) error {
ino: ino,
path: dir,
names: make(map[string]uint64),
buf: make([]byte, bufsize),
}
w.mu.Lock()
w.watches.set(ino, watchEntry)
Expand Down Expand Up @@ -543,8 +562,11 @@ func (w *Watcher) startRead(watch *watch) error {
return nil
}

rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
// We need to pass the array, rather than the slice.
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
(*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
false, mask, nil, &watch.ov, 0)
if rdErr != nil {
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
Expand Down Expand Up @@ -603,7 +625,7 @@ func (w *Watcher) readEvents() {
case in := <-w.input:
switch in.op {
case opAddWatch:
in.reply <- w.addWatch(in.path, uint64(in.flags))
in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize)
case opRemoveWatch:
in.reply <- w.remWatch(in.path)
}
Expand Down
30 changes: 30 additions & 0 deletions fsnotify.go
Expand Up @@ -101,3 +101,33 @@ func (e Event) Has(op Op) bool { return e.Op.Has(op) }
func (e Event) String() string {
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
}

type (
addOpt func(opt *withOpts)
withOpts struct {
bufsize int
}
)

var defaultOpts = withOpts{
bufsize: 65536, // 64K
}

func getOptions(opts ...addOpt) withOpts {
with := defaultOpts
for _, o := range opts {
o(&with)
}
return with
}

// WithBufferSize sets the buffer size for the Windows backend. This is a no-op
// for other backends.
//
// The default value is 64K (65536 bytes) which is the highest value that works
// on all filesystems and should be enough for most applications, but if you
// have a large burst of events it may not be enough. You can increase it if
// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]).
func WithBufferSize(bytes int) addOpt {
return func(opt *withOpts) { opt.bufsize = bytes }
}
16 changes: 15 additions & 1 deletion mkdoc.zsh
Expand Up @@ -80,6 +80,8 @@ add=$(<<EOF
//
// Returns [ErrClosed] if [Watcher.Close] was called.
//
// See [AddWith] for a version that allows adding options.
//
// # Watching directories
//
// All files in a directory are monitored, including new files that are created
Expand All @@ -100,6 +102,17 @@ add=$(<<EOF
EOF
)

addwith=$(<<EOF
// AddWith is like [Add], but allows adding options. When using Add() the
// defaults described below are used.
//
// Possible options are:
//
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
// other platforms. The default is 64K (65536 bytes).
EOF
)

remove=$(<<EOF
// Remove stops monitoring the path for changes.
//
Expand Down Expand Up @@ -166,7 +179,7 @@ errors=$(<<EOF
// [ErrEventOverflow] is used to indicate there are too many events:
//
// - inotify: there are too many queued events (fs.inotify.max_queued_events sysctl)
// - windows: The buffer size is too small.
// - windows: The buffer size is too small; [WithBufferSize] can be used to increase it.
// - kqueue, fen: not used.
EOF
)
Expand Down Expand Up @@ -202,6 +215,7 @@ set-cmt() {
set-cmt '^type Watcher struct ' $watcher
set-cmt '^func NewWatcher(' $new
set-cmt '^func (w \*Watcher) Add(' $add
set-cmt '^func (w \*Watcher) AddWith(' $addwith
set-cmt '^func (w \*Watcher) Remove(' $remove
set-cmt '^func (w \*Watcher) Close(' $close
set-cmt '^func (w \*Watcher) WatchList(' $watchlist
Expand Down