diff --git a/backend_fen.go b/backend_fen.go index 95fbe99a..1f4dcf77 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -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 @@ -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 @@ -212,7 +214,16 @@ 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 } @@ -220,6 +231,8 @@ func (w *Watcher) Add(name string) error { 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) diff --git a/backend_inotify.go b/backend_inotify.go index a96c3adf..16ac36e1 100644 --- a/backend_inotify.go +++ b/backend_inotify.go @@ -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 @@ -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 @@ -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 diff --git a/backend_kqueue.go b/backend_kqueue.go index cbe4778b..f9a0951f 100644 --- a/backend_kqueue.go +++ b/backend_kqueue.go @@ -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 @@ -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 @@ -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() diff --git a/backend_other.go b/backend_other.go index fc6bacae..60d57053 100644 --- a/backend_other.go +++ b/backend_other.go @@ -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 } @@ -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 @@ -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 diff --git a/backend_windows.go b/backend_windows.go index ba5e9581..e99139e7 100644 --- a/backend_windows.go +++ b/backend_windows.go @@ -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 @@ -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 @@ -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 { @@ -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 { @@ -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 ( @@ -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 @@ -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) @@ -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 { @@ -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) } diff --git a/fsnotify.go b/fsnotify.go index 4f7f445f..142169da 100644 --- a/fsnotify.go +++ b/fsnotify.go @@ -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 } +} diff --git a/mkdoc.zsh b/mkdoc.zsh index 19aa1edf..02216e74 100755 --- a/mkdoc.zsh +++ b/mkdoc.zsh @@ -80,6 +80,8 @@ add=$(<