diff --git a/backend_fen.go b/backend_fen.go index b66bd373..4d0716f1 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -114,7 +114,7 @@ type Watcher struct { // [ErrEventOverflow] is used to indicate ther 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,7 +223,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 has the possibility to add 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 } @@ -229,6 +240,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 95811156..701e5258 100644 --- a/backend_inotify.go +++ b/backend_inotify.go @@ -117,7 +117,7 @@ type Watcher struct { // [ErrEventOverflow] is used to indicate ther 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 @@ -226,6 +226,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 @@ -243,12 +245,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 has the possibility to add 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 27a407a4..4733f215 100644 --- a/backend_kqueue.go +++ b/backend_kqueue.go @@ -115,7 +115,7 @@ type Watcher struct { // [ErrEventOverflow] is used to indicate ther 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 @@ -258,6 +258,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 @@ -275,7 +277,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 has the possibility to add 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 e2b21427..0890675d 100644 --- a/backend_other.go +++ b/backend_other.go @@ -39,6 +39,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 @@ -58,6 +60,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 has the possibility to add 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 3f7e1836..10799d51 100644 --- a/backend_windows.go +++ b/backend_windows.go @@ -118,7 +118,7 @@ type Watcher struct { // [ErrEventOverflow] is used to indicate ther 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 @@ -213,6 +213,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 @@ -230,16 +232,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 has the possibility to add 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 { @@ -338,10 +355,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 { @@ -357,7 +375,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 ( @@ -430,7 +448,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 @@ -453,6 +471,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) @@ -552,8 +571,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 { @@ -612,7 +634,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 37b34315..7f9ac827 100644 --- a/fsnotify.go +++ b/fsnotify.go @@ -84,3 +84,32 @@ 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 should be enough for most +// applications, but 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 157f425c..6351713b 100755 --- a/mkdoc.zsh +++ b/mkdoc.zsh @@ -89,6 +89,8 @@ add=$(<