watch: Unsubscribe from fsnotify synchronously.

Calling watcher.Remove() from the run() goroutine is now problematic,
because with the change made in fsnotify/fsnotify#73 Remove() can now
take an arbitrary amount of time, which means we can deadlock if run()
is waiting for fsnotify to acknowledge the removal and fsnotify is
trying to send an unrelated Event.

So instead we now do part of the cleanup, including calling Remove(),
synchronously, in the goroutine trying to unsubscribe.

This fixes #75.  Thanks to Aaron Beitch for the fix.

Change-Id: I346c9eecc34b2378312b07b3c3efc41616b95380
This commit is contained in:
Benoit Sigoure 2016-02-29 19:00:42 -08:00
parent 1a0242e795
commit 0bc5f080e8
1 changed files with 18 additions and 13 deletions

View File

@ -107,8 +107,26 @@ func remove(winfo *watchInfo) {
delete(shared.done, winfo.fname)
close(done)
}
fname := winfo.fname
if winfo.isCreate() {
// Watch for new files to be created in the parent directory.
fname = filepath.Dir(fname)
}
shared.watchNums[fname]--
watchNum := shared.watchNums[fname]
if watchNum == 0 {
delete(shared.watchNums, fname)
}
shared.mux.Unlock()
// If we were the last ones to watch this file, unsubscribe from inotify.
// This needs to happen after releasing the lock because fsnotify waits
// synchronously for the kernel to acknowledge the removal of the watch
// for this file, which causes us to deadlock if we still held the lock.
if watchNum == 0 {
shared.watcher.Remove(fname)
}
shared.remove <- winfo
}
@ -174,19 +192,6 @@ func (shared *InotifyTracker) removeWatch(winfo *watchInfo) {
return
}
fname := winfo.fname
if winfo.isCreate() {
// Watch for new files to be created in the parent directory.
fname = filepath.Dir(fname)
}
shared.watchNums[fname]--
if shared.watchNums[fname] == 0 {
delete(shared.watchNums, fname)
// TODO: handle error
shared.watcher.Remove(fname)
}
delete(shared.chans, winfo.fname)
close(ch)