Merge branch 'vsco-fix-log-reopen-with-inotify'
diff --git a/tail_test.go b/tail_test.go
index 8dcc02c..38d6b84 100644
--- a/tail_test.go
+++ b/tail_test.go
@@ -343,13 +343,21 @@
"test.txt",
Config{Follow: true, ReOpen: true, Poll: poll})
content := []string{"hello", "world", "more", "data", "endofworld"}
- go tailTest.ReadLines(tail, content)
+ go tailTest.VerifyTailOutput(tail, content, false)
- // deletion must trigger reopen
- <-time.After(delay)
- tailTest.RemoveFile("test.txt")
- <-time.After(delay)
- tailTest.CreateFile("test.txt", "more\ndata\n")
+ if poll {
+ // deletion must trigger reopen
+ <-time.After(delay)
+ tailTest.RemoveFile("test.txt")
+ <-time.After(delay)
+ tailTest.CreateFile("test.txt", "more\ndata\n")
+ } else {
+ // In inotify mode, fsnotify is currently unable to deliver notifications
+ // about deletion of open files, so we are not testing file deletion.
+ // (see https://github.com/fsnotify/fsnotify/issues/194 for details).
+ <-time.After(delay)
+ tailTest.AppendToFile("test.txt", "more\ndata\n")
+ }
// rename must trigger reopen
<-time.After(delay)
@@ -366,7 +374,34 @@
// Do not bother with stopping as it could kill the tomb during
// the reading of data written above. Timings can vary based on
// test environment.
- tail.Cleanup()
+ tailTest.Cleanup(tail, false)
+}
+
+func TestInotify_WaitForCreateThenMove(t *testing.T) {
+ tailTest := NewTailTest("wait-for-create-then-reopen", t)
+ os.Remove(tailTest.path + "/test.txt") // Make sure the file does NOT exist.
+
+ tail := tailTest.StartTail(
+ "test.txt",
+ Config{Follow: true, ReOpen: true, Poll: false})
+
+ content := []string{"hello", "world", "endofworld"}
+ go tailTest.VerifyTailOutput(tail, content, false)
+
+ time.Sleep(50 * time.Millisecond)
+ tailTest.CreateFile("test.txt", "hello\nworld\n")
+ time.Sleep(50 * time.Millisecond)
+ tailTest.RenameFile("test.txt", "test.txt.rotated")
+ time.Sleep(50 * time.Millisecond)
+ tailTest.CreateFile("test.txt", "endofworld\n")
+ time.Sleep(50 * time.Millisecond)
+ tailTest.RemoveFile("test.txt.rotated")
+ tailTest.RemoveFile("test.txt")
+
+ // Do not bother with stopping as it could kill the tomb during
+ // the reading of data written above. Timings can vary based on
+ // test environment.
+ tailTest.Cleanup(tail, false)
}
func reSeek(t *testing.T, poll bool) {
@@ -426,6 +461,13 @@
}
}
+func (t TailTest) AppendToFile(name string, contents string) {
+ err := ioutil.WriteFile(t.path+"/"+name, []byte(contents), 0600|os.ModeAppend)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
func (t TailTest) RemoveFile(name string) {
err := os.Remove(t.path + "/" + name)
if err != nil {
diff --git a/watch/inotify_tracker.go b/watch/inotify_tracker.go
index 4f85217..d68ab61 100644
--- a/watch/inotify_tracker.go
+++ b/watch/inotify_tracker.go
@@ -108,28 +108,10 @@
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 {
- return shared.watcher.Remove(fname)
- }
shared.remove <- winfo
- return nil
+ return <-shared.error
}
// Events returns a channel to which FileEvents corresponding to the input filename
@@ -155,6 +137,8 @@
if shared.chans[winfo.fname] == nil {
shared.chans[winfo.fname] = make(chan fsnotify.Event)
+ }
+ if shared.done[winfo.fname] == nil {
shared.done[winfo.fname] = make(chan bool)
}
@@ -164,47 +148,50 @@
fname = filepath.Dir(fname)
}
+ var err error
// already in inotify watch
- if shared.watchNums[fname] > 0 {
- shared.watchNums[fname]++
- if winfo.isCreate() {
- shared.watchNums[winfo.fname]++
- }
- return nil
+ if shared.watchNums[fname] == 0 {
+ err = shared.watcher.Add(fname)
}
-
- err := shared.watcher.Add(fname)
if err == nil {
shared.watchNums[fname]++
- if winfo.isCreate() {
- shared.watchNums[winfo.fname]++
- }
}
return err
}
// removeWatch calls fsnotify.RemoveWatch for the input filename and closes the
// corresponding events channel.
-func (shared *InotifyTracker) removeWatch(winfo *watchInfo) {
+func (shared *InotifyTracker) removeWatch(winfo *watchInfo) error {
shared.mux.Lock()
- defer shared.mux.Unlock()
ch := shared.chans[winfo.fname]
- if ch == nil {
- return
+ if ch != nil {
+ delete(shared.chans, winfo.fname)
+ close(ch)
}
- delete(shared.chans, winfo.fname)
- close(ch)
+ 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 !winfo.isCreate() {
- return
+ var err error
+ // 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 {
+ err = shared.watcher.Remove(fname)
}
- shared.watchNums[winfo.fname]--
- if shared.watchNums[winfo.fname] == 0 {
- delete(shared.watchNums, winfo.fname)
- }
+ return err
}
// sendEvent sends the input event to the appropriate Tail.
@@ -239,7 +226,7 @@
shared.error <- shared.addWatch(winfo)
case winfo := <-shared.remove:
- shared.removeWatch(winfo)
+ shared.error <- shared.removeWatch(winfo)
case event, open := <-shared.watcher.Events:
if !open {