From 0bc5f080e83915b4d756f9bfc2db7ea3b696fdd8 Mon Sep 17 00:00:00 2001 From: Benoit Sigoure Date: Mon, 29 Feb 2016 19:00:42 -0800 Subject: [PATCH] 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 --- watch/inotify_tracker.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/watch/inotify_tracker.go b/watch/inotify_tracker.go index f53f2c3..167cd02 100644 --- a/watch/inotify_tracker.go +++ b/watch/inotify_tracker.go @@ -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)