Martin Tournoij [Fri, 14 Oct 2022 16:34:36 +0000 (18:34 +0200)]
inotify: remove watch when renaming a watched path (#518)
Renaming a watched path is problematic; on inotify we just get a
IN_MOVED_SELF event with the old filename and that's it; no more events
for you! So if you do:
watch one
mv one two
cat asd >two
You still continue to get events for the file "one", even though it's
now named "two" (the file descriptor doesn't care about the rename).
There is no way we can know the new event as far as I can tell,
inotifywait(1) also behaves like this. So instead of continuing in a
semi-broken state just remove the watcher, like we do for deletes.
On kqueue and FEN the situation is similar, and we actually already
removed watchers on renames.
On Windows this all works nicely; the watch is preserved and the
filename is updated. I decided to keep this as-is for now, even though
it's inconsistent. We actually fixed the Windows behaviour for the 1.6.0
release in #370 , so people do seem to care about it and use it, and
experience with the symlink change in 1.5.0 shows it's better to keep
inconsistent behaviour rather than change it. Fixing this up is
something for a v2.
Martin Tournoij [Thu, 13 Oct 2022 18:17:08 +0000 (20:17 +0200)]
Return ErrClosed on Add() when the watcher is closed (#516)
Also consistently return nil for Remove() and WatchList(). For Remove()
we could return an error, but I don't really see the point: if the
watcher is closed it's basically a no-op so returning nil is fine.
For WatchList() I'd like to return an error, but there is no error
return, so explicitly returning nil is the best we can do.
Martin Tournoij [Wed, 10 Aug 2022 15:10:01 +0000 (17:10 +0200)]
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.
Martin Tournoij [Mon, 8 Aug 2022 13:56:08 +0000 (15:56 +0200)]
Move some inotify-tests to run on all backends; test that state is cleaned up after Remove (#494)
This moves a number of tests from backend_inotify_test.go to
fsnotify_test.go, as they're not really inotify-specific. In particular,
it moves the "stress test", which creates a bunch of events/files. It
also expands this test to (potentially) create many more than just 1,000
files, depending on how many the system will allow.
Unfortunately these tests seem pretty flaky on kqueue platforms, where
they're allowed to fail for now (failing the test won't fail the test
run). This seems to expose some existing limits/problems that need to
fixed in a future PR.
Also test that the internal state is cleaned up with TestRemoveState().
The Windows backend doesn't have a test for it (or rather, it doesn't
run) as it *doesn't* clean the state properly, but I found it too
confusing to fix 🤷 Need to spend some time on that in the future.
Reorder/rename some GitHub Actions test runs to show nicer in the UI.
Martin Tournoij [Sat, 6 Aug 2022 18:03:28 +0000 (20:03 +0200)]
Port minor test changes from fen-v2 branch; make LICENSE text not ugly
Also remove the copyright headers on the files; the license text
doesn't require them, forbids removing them, and they add nothing IMHO,
and it's a few more lines to scroll past every time you open a file.
Martin Tournoij [Sat, 6 Aug 2022 17:56:40 +0000 (19:56 +0200)]
windows: simplify a bit (#493)
The sys* constants were used in the old golang.org/x/exp/winfsnotify
package on which this is based, but haven't been in use for a long time.
Remove the "one shot", "dir only", and "ignore" options as they were
never set. Also don't send "sysFSQOVERFLOW" as an event if the buffer is
too long; we already send an error and it's just a weird magical number
you get.
This could be simplified greatly more by removing the whole
sysFS… → windows.FILE_NOTIFY… → sysFS… conversions, but that's for
another day.
Martin Tournoij [Sat, 6 Aug 2022 16:40:15 +0000 (18:40 +0200)]
kqueue: don't set up watchers on unreadable files (#479)
Watching an entire directory would fail if it contains a file we can't
read due to permission errors. That's obviously not ideal, so just skip
the file.
Also add a test for trying to watch a directory where the permissions
are denied, and test the error is what's expected; this prevents
regressions like in #393.
Martin Tournoij [Sat, 6 Aug 2022 16:17:18 +0000 (18:17 +0200)]
kqueue: remove timeout from unix.Kevent() (#480)
The timeout for unix.Kevent() is causing issues; every 100ms it will do a new
unix.Kevent() syscall, which isn't too efficient: even if you have just one
change an hour, you will still keep calling kevent() ten times per second,
resulting in a needlessly high CPU usage.
Without a timeout, kevent() will block indefinitely until there are some events,
which is much more efficient. We can't just remove the timout however, since we
can't interrupt the kevent() call on FreeBSD and NetBSD, and it will hang
forever. This issue is described in more detail here:
https://github.com/fsnotify/fsnotify/pull/262#issuecomment-1201655890
To solve this we register a new kevent() with the file descriptor set to the
closepipe; when this pipe is closed an event is sent and kevent() will stop
blocking.
This is a rebased version of #124.
Fixes #89
Fixes #237
Fixes #333
Supersedes and closes #124
Supersedes and closes #262
Supersedes and closes #334
Martin Tournoij [Fri, 5 Aug 2022 19:52:45 +0000 (21:52 +0200)]
windows: run TestWatchRename/rename_overwriting_existing_file (#490)
This Skip() was added in 2013 (c825c6d) with "Windows cannot overwrite
file on rename", but it seems to work fine. os.Rename() is also
documented as "If newpath already exists and is not a directory, Rename
replaces it", so if it *doesn't* work it's a bug in os.Rename(). Maybe
os.Rename() behaved different nine years ago.
Martin Tournoij [Fri, 5 Aug 2022 15:03:18 +0000 (17:03 +0200)]
Use "os.Rename()" in tests instead of "mv"
This was added in 00f2dfd (2011): "BSD Test: Need to use outside program
to trigger some events". All tests pass fine with just os.Rename(), so
just use that.
Might add another real-world test in the future for common tools like
mv, cp, vi, etc. because they don't always behave the same as library
calls, but calling mv in *only* the mv() helper is rather odd.
This also renames a few things in the kqueue helper for clarity, and
uses map[string]struct{} if we just use them for lookups to an
allocation (I originally tried to fix #488, but gave up on that for now,
hence the kqueue changes).
Martin Tournoij [Thu, 4 Aug 2022 00:38:52 +0000 (02:38 +0200)]
windows: replace syscall with golang.org/x/sys/windows
Specifically to address #364; syscall.GetQueuedCompletionStatus()
contains a bug that can't be fixed in a backwards-compatible way, so
they didn't. Once you use sys/windows for one thing, you gotta use it
for everything.
I ran the tests with -count=100 locally, and it all seems fine.
Martin Tournoij [Thu, 4 Aug 2022 01:25:38 +0000 (03:25 +0200)]
windows: increase buffer size from 4K to 64K (#485)
People are running in to trouble because the 4K buffer can overflow;
see: #72.
This is not a "real" fix, but I think a 64K buffer is acceptable even on
memory-limited machines; no one is running the Windows on an Arduino,
and even with something like 128M of memory, the extra 124K is basically
negligible. There is also no real performance difference between
allocating a large array vs. a small array: they're both comparable.
It should probably be enough for most applications. Need to look in the
future to either dynamically grow the size, or allow setting it similar
to what tilt does as mentioned in #72.
Martin Tournoij [Sun, 31 Jul 2022 18:03:10 +0000 (20:03 +0200)]
Rewrite tests (#478)
Rewrite tests
This rewrites quite a lot of tests to be much more easily readable.
While working on #472 I wanted to check "how do renames behave now?",
and found this quite hard as most test cases were >90% "plumbing", and
seeing "what file operations does this do?" and "what events do we get?"
was not very easy.
So refactor the lot, based on some work I did in #472:
- Add a bunch of "shell-like" helper functions so you're not forever
typing error checks, filepath.Join(), and eventSeparator(). Just
touch(t, tmp, "file") will create a file in tmp.
- Add eventCollector type which will collect all events in in a slice,
replacing the previous "counter". This also ensures that the Watcher
is closed within a second (this removes a lot of duplicate code).
This is also much more precise than merely counting events; before
random events could get emitted but if you weren't counting those then
you'd never know.
Downside is that some tests are a bit more flaky now as some
behaviours are not always consistent in various edge cases; these are
pre-existing bugs.
- Add Events (plural) type (only for tests), and helper function to
create this from a string like:
REMOVE /link
CREATE /link
WRITE /link
Which makes seeing which events are received, diffing them, etc. much
easier.
- Add Parallel() for most tests; reduces runtime on my system from ~12
seconds to ~6 seconds.
All in all it reduces the integrations_test.go from 1279 lines to 405
lines and it's quite easy to see which events are expected for which
operations, which should make things a lot easier going forward.
Martin Tournoij [Sat, 30 Jul 2022 10:54:27 +0000 (12:54 +0200)]
macos: retry if open() returns EINTR (#475)
Retry the unix.Open() if the error returned is EINTR; looking around the
web it seems many systems handle it like this. This is also what
os.Open() does:
https://github.com/golang/go/commit/50d0ee0c98ea21f818d2daa9bc21ef51861a2ef9
Martin Tournoij [Fri, 29 Jul 2022 21:00:44 +0000 (23:00 +0200)]
Remove AUTHORS file
This goes back to when this library was part of golang.org/x/...; the "Google lawyers" said that an AUTHORS file was needed, but seems they changed their minds and it got removed from Go last week and decided that the git log is enough:
https://github.com/golang/go/commit/774fa58d1d3c9926709e108afdf83af7010bde72
I don't think there's much value keeping it here, and it's one more thing that can be forgotten (it's already out of date). The authorship is preserved in the git log, and if that's good enough for Google's lawyers then it's good enough for me.
Martin Tournoij [Fri, 29 Jul 2022 18:31:09 +0000 (20:31 +0200)]
inotify: don't ignore events for files that don't exist (#470)
This is quite an odd check, leading to an inconsistent event stream,
which also doesn't match what other platforms do. If you do a "CREATE +
MODIFY + REMOVE" in quick succession then you probably *want* all three
events. If you don't want to operate on non-existing files, then you can
check this in your application code.
You need to do that already, since this check is far from reliable. In
the time between this check and the application code doing something
with an event the file may have been deleted already.
I looked a bit at the history of this, and looks like it was added in
2013 with cc2c34e; issue 36 refers to this issue on the old repo, which
mentions it fixes a memory leak: https://github.com/howeyc/fsnotify/issues/36
I can't reproduce that at all; using the CLI from #463 modified to print
the memory and running:
for i in $(seq 0 10000); { touch $i; rm $i }
Memory stays at about 100/110K in both the current main branch and this.
It's mostly the same as the example in the README, except that it takes
paths from the commandline and aligns things a bit nicer.
This is useful for documentation and experimentation. For example, while
reviewing some PRs I found it useful to quickly get an overview of "what
does fsnotify do now, and what does it do with this patch?"
Martin Tournoij [Fri, 29 Jul 2022 18:28:22 +0000 (20:28 +0200)]
kqueue: better error if watching a file fails (#471)
kqueue requires opening a file descriptor for every file; so we read the
directory and do that. If this failed the error was quite unclear; for
example:
Martin Tournoij [Sun, 24 Jul 2022 09:22:52 +0000 (11:22 +0200)]
Test some more things in CI (#469)
* Test some more things in CI
- Test all GOOS/GOARCH combinations by looping over "go tool dist list";
this just tests if it compiles.
- Add a Vagrant box to test Debian 6 / Linux 2.6.32; this was adapted
from @horahoradev's patch at #434.
- Update the minimum version requirements: we test Linux 2.6.32 now and
turns out that's also the minimum version [Go supports] in recent
releases, so just set it to that.
Need Go 1.16 for retract in go.mod. I don't know, maybe we can just
remove the retract? Latest Debian ships with Go 1.15.
- Test both Go 1.16 and 1.18 (the lowest supported version and newest
version). I guess we could also test 1.17, but the CI already takes
somewhat long and I can't recall ever having a situation where an
intermediate version failed.
- Test macOS 11 and 12; macOS 10.15 will (probably) end support in
November, so probably not worth supporting this. GitHub will [remove]
support for this at the end of August.
- Actually run tests for all systems Go supports. Bit pointless right
now as many of these don't do anything. Currently untested are
Solaris, illumios, plan9, AIX, Android, iOS, DragonflyBSD, WASM.
Some of these might be difficult (AIX, iOS, Android), but haven't
looked in to it. I tried setting up Solaris with the
vmactions/solaris, but it doesn't seem to have an easy way to install
Go.
- GitHub only [supports] Windows Server 2019 and 2022; probabably also
want to test Server 2016, but GitHub dropped support for this. Can
maybe use AppVeyor(?)
Martin Tournoij [Fri, 22 Jul 2022 05:22:49 +0000 (07:22 +0200)]
inotify: fix race in Close() (#465)
Would sometimes fail with:
panic: close of closed channel
goroutine 204 [running]:
github.com/fsnotify/fsnotify.(*Watcher).Close(0xc0003e6410)
/home/martin/code/Golib/fsnotify/inotify.go:82 +0x66
created by github.com/fsnotify/fsnotify.TestCloseRace
/home/martin/code/Golib/fsnotify/integration_test.go:1256 +0x1a5
exit status 2
Because isClosed() might return "false" for two goroutines, and then
they will both try to close it, resulting in the panic.
Kir Kolyshkin [Thu, 21 Jul 2022 11:32:11 +0000 (04:32 -0700)]
Do not suppress Chmod on non-existent file (#260)
Currently fsnotify suppresses a Chmod event if the file does not exist
when the event is received.
This makes it impossible to use fsnotify to detect when an opened file
is removed. In such case the Linux kernel sends IN_ATTRIB event,
as described in inotify(7) man page:
> IN_ATTRIB (*)
> Metadata changed—for example, permissions (e.g., chmod(2)),
> timestamps (e.g., utimensat(2)), extended attributes (setx‐
> attr(2)), link count (since Linux 2.6.25; e.g., for the tar‐
> get of link(2) and for unlink(2)), and user/group ID (e.g.,
> chown(2)).
(to clarify, in this very case it's link count that changes).
To fix:
* Modify the code to only suppress MODIFY and CREATE events.
* Add a test case to verify Chmod event is delivered.
While at it, fix the comment in ignoreLinux() to use the up-to-date
terminology (event ops).
A test case is added.
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Nick Santos [Thu, 21 Jul 2022 10:20:14 +0000 (11:20 +0100)]
kqueue: Make watcher.Close() O(n) instead of O(n^2) (#233)
In the old implementation watcher.Close() would clone the list of
watches and then run Remove() on that, and every Remove() call would
iterate over the full list of watches.
This stores the watches more efficiently so that Remove() doesn't need
to iterate over the full list: instead of merely a path → fd map, it
also stores a parent-path → list-of-files.
No functional changes, just a performance improvement.
Martin Tournoij [Thu, 21 Jul 2022 03:06:57 +0000 (05:06 +0200)]
Use common error when removing an unwatched file (#460)
The errors returned by the various implementations of the watcher are all different
which makes handling them difficult. This PR follows the suggestion in:
https://github.com/fsnotify/fsnotify/pull/455#issuecomment-1146037157 by @mattn
to create a common error which is wrapped by the implementations.
Replaces: https://github.com/fsnotify/fsnotify/pull/455 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Andrew Thornton <art27@cantab.net>
Robert Fratto [Thu, 21 Jul 2022 03:06:40 +0000 (23:06 -0400)]
windows: protect access to isClosed with mutex (#454)
isClosed may be read and modified concurrently if the Windows Watcher is
closed at the same time as a new entry being added. This change protects
isClosed to prevent race conditions.
The order of fields for the Windows Watcher have been rearranged to
group fields protected by the mutex together.
Tobias Klauser [Tue, 25 Jan 2022 02:13:06 +0000 (03:13 +0100)]
Allow build on unsupported GOOS (#424)
In cases where fsnotify is a transitive dependency of a package, it
might need to be built on platforms not supported (yet), e.g. aix.
Provide an empty implemention for these cases, so depending packages
don't need to add workarounds.
Tobias Klauser [Wed, 19 Jan 2022 14:22:52 +0000 (15:22 +0100)]
Don't set poller.fd twice in newFdPoller (#406)
In newEmptyPoller when creating the poller using newEmptyPoller(fd), the
fd field of the poller instance is already set. There is no need to set
it again.