From e9c3c07fbbf7fab3e74b46181e3da680c2523b1f Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Tue, 28 May 2013 11:46:49 -0700 Subject: [PATCH 1/8] add (failing) tests for copytruncate detection --- tail_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/tail_test.go b/tail_test.go index 56af1e5..a4f214e 100644 --- a/tail_test.go +++ b/tail_test.go @@ -128,14 +128,60 @@ func _TestReOpen(_t *testing.T, poll bool) { // The use of polling file watcher could affect file rotation // (detected via renames), so test these explicitly. +func TestReOpenWithoutPoll(_t *testing.T) { + _TestReOpen(_t, false) +} + func TestReOpenWithPoll(_t *testing.T) { _TestReOpen(_t, true) } -func TestReOpenWithoutPoll(_t *testing.T) { - _TestReOpen(_t, false) + +func _TestReSeek(_t *testing.T, poll bool) { + var name string + if poll { + name = "reseek-polling" + }else { + name = "reseek-inotify" + } + t := NewTailTest(name, _t) + t.CreateFile("test.txt", "hello\nworld\n") + tail := t.StartTail( + "test.txt", + Config{Follow: true, ReOpen: true, Poll: poll, Location: -1}) + + go t.VerifyTailOutput(tail, []string{"hello", "world", "h311o", "w0r1d", "endofworld"}) + + // truncate now + <-time.After(100 * time.Millisecond) + t.TruncateFile("test.txt", "h311o\nw0r1d\nendofworld\n") + // XXX: is this required for this test function? + if poll { + // Give polling a chance to read the just-written lines (more; + // data), before we recreate the file again below. + <-time.After(POLL_DURATION) + } + + // Delete after a reasonable delay, to give tail sufficient time + // to read all lines. + <-time.After(100 * time.Millisecond) + t.RemoveFile("test.txt") + + tail.Stop() } +// The use of polling file watcher could affect file rotation +// (detected via renames), so test these explicitly. + +func TestReSeekWithoutPoll(_t *testing.T) { + _TestReSeek(_t, false) +} + +func TestReSeekWithPoll(_t *testing.T) { + _TestReSeek(_t, true) +} + + // Test library type TailTest struct { @@ -188,6 +234,18 @@ func (t TailTest) AppendFile(name string, contents string) { } } +func (t TailTest) TruncateFile(name string, contents string) { + f, err := os.OpenFile(t.path+"/"+name, os.O_WRONLY, 0600) + if err != nil { + t.Fatal(err) + } + defer f.Close() + _, err = f.WriteString(contents) + if err != nil { + t.Fatal(err) + } +} + func (t TailTest) StartTail(name string, config Config) *Tail { tail, err := TailFile(t.path+"/"+name, config) if err != nil { @@ -211,7 +269,8 @@ func (t TailTest) VerifyTailOutput(tail *Tail, lines []string) { t.Fatalf("tail.Lines returned nil; not possible") } if tailedLine.Text != line { - t.Fatalf("mismatch; %s != %s", tailedLine.Text, line) + t.Fatalf("mismatch; %s (actual) != %s (expected)", + tailedLine.Text, line) } } line, ok := <-tail.Lines From 1ff299bc29222d0b8b43b858124cc24b00fa4cbe Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Fri, 10 May 2013 14:18:00 -0400 Subject: [PATCH 2/8] Add support for file truncation in InotifyFileWatcher (cherry picked from commit 9de77aad8caca8102e7dd7c936d00ca3f0421ca7) --- tail.go | 2 +- watch.go | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/tail.go b/tail.go index c68d7b2..8e8a71a 100644 --- a/tail.go +++ b/tail.go @@ -190,7 +190,7 @@ func (tail *Tail) tailFileSync() { // File got deleted/renamed if tail.ReOpen { // TODO: no logging in a library? - log.Printf("Re-opening moved/deleted file %s ...", tail.Filename) + log.Printf("Re-opening moved/deleted/truncated file %s ...", tail.Filename) err := tail.reopen() if err != nil { tail.close() diff --git a/watch.go b/watch.go index df99e25..cf653d6 100644 --- a/watch.go +++ b/watch.go @@ -24,10 +24,11 @@ type FileWatcher interface { // InotifyFileWatcher uses inotify to monitor file changes. type InotifyFileWatcher struct { Filename string + Size int64 } func NewInotifyFileWatcher(filename string) *InotifyFileWatcher { - fw := &InotifyFileWatcher{filename} + fw := &InotifyFileWatcher{filename, 0} return fw } @@ -65,19 +66,29 @@ func (fw *InotifyFileWatcher) ChangeEvents(_ os.FileInfo) chan bool { ch := make(chan bool) go func() { + defer w.Close() + defer w.RemoveWatch(fw.Filename) + defer close(ch) + for { + prevSize := fw.Size + evt := <-w.Event switch { case evt.IsDelete(): fallthrough case evt.IsRename(): - close(ch) - w.RemoveWatch(fw.Filename) - w.Close() return case evt.IsModify(): + fi, _ := os.Stat(fw.Filename) + fw.Size = fi.Size() + + if prevSize > 0 && prevSize > fw.Size { + return + } + // send only if channel is empty. select { case ch <- true: @@ -93,10 +104,11 @@ func (fw *InotifyFileWatcher) ChangeEvents(_ os.FileInfo) chan bool { // PollingFileWatcher polls the file for changes. type PollingFileWatcher struct { Filename string + Size int64 } func NewPollingFileWatcher(filename string) *PollingFileWatcher { - fw := &PollingFileWatcher{filename} + fw := &PollingFileWatcher{filename, 0} return fw } From 8c443fb0675f149271d63c9b2b5a853362888560 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Tue, 28 May 2013 12:28:56 -0700 Subject: [PATCH 3/8] fix truncation tests * use O_TRUNC to actually truncate * use longer initial data before truncating truncated and updated file is expected to be of smaller size, else truncation detection won't work. ref: https://github.com/josegonzalez/beaver/pull/67/files --- tail_test.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tail_test.go b/tail_test.go index a4f214e..57cec46 100644 --- a/tail_test.go +++ b/tail_test.go @@ -128,11 +128,11 @@ func _TestReOpen(_t *testing.T, poll bool) { // The use of polling file watcher could affect file rotation // (detected via renames), so test these explicitly. -func TestReOpenWithoutPoll(_t *testing.T) { +func TestReOpenInotify(_t *testing.T) { _TestReOpen(_t, false) } -func TestReOpenWithPoll(_t *testing.T) { +func TestReOpenPolling(_t *testing.T) { _TestReOpen(_t, true) } @@ -145,15 +145,22 @@ func _TestReSeek(_t *testing.T, poll bool) { name = "reseek-inotify" } t := NewTailTest(name, _t) - t.CreateFile("test.txt", "hello\nworld\n") + t.CreateFile("test.txt", "a really long string goes here\nhello\nworld\n") tail := t.StartTail( "test.txt", Config{Follow: true, ReOpen: true, Poll: poll, Location: -1}) - go t.VerifyTailOutput(tail, []string{"hello", "world", "h311o", "w0r1d", "endofworld"}) + go t.VerifyTailOutput(tail, []string{ + "a really long string goes here", "hello", "world", "h311o", "w0r1d", "endofworld"}) // truncate now <-time.After(100 * time.Millisecond) + if poll { + // Give polling a chance to read the just-written lines (more; + // data), before we truncate the file again below. + <-time.After(POLL_DURATION) + } + println("truncating..") t.TruncateFile("test.txt", "h311o\nw0r1d\nendofworld\n") // XXX: is this required for this test function? if poll { @@ -165,19 +172,20 @@ func _TestReSeek(_t *testing.T, poll bool) { // Delete after a reasonable delay, to give tail sufficient time // to read all lines. <-time.After(100 * time.Millisecond) - t.RemoveFile("test.txt") + // XXX t.RemoveFile("test.txt") + println("Stopping...") tail.Stop() } // The use of polling file watcher could affect file rotation // (detected via renames), so test these explicitly. -func TestReSeekWithoutPoll(_t *testing.T) { +func TestReSeekInotify(_t *testing.T) { _TestReSeek(_t, false) } -func TestReSeekWithPoll(_t *testing.T) { +func TestReSeekPolling(_t *testing.T) { _TestReSeek(_t, true) } @@ -235,7 +243,7 @@ func (t TailTest) AppendFile(name string, contents string) { } func (t TailTest) TruncateFile(name string, contents string) { - f, err := os.OpenFile(t.path+"/"+name, os.O_WRONLY, 0600) + f, err := os.OpenFile(t.path+"/"+name, os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { t.Fatal(err) } From 5ccafcc3d6731f0c462cd9d5c1386f4187f779f4 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Tue, 28 May 2013 12:31:46 -0700 Subject: [PATCH 4/8] fixes on top of Florian's truncation detection (1ff299bc2) * initialize `Size` with the then-size of the file when ChangeEvents is called. remember that this function is expected to be called many times, one after another. this also passes the TestReSeekInotify test. * do not ignore errors from os.Stat. we panic errors like these now, but ideally should switch to tomb.Tomb for letting the user handle them. --- watch.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/watch.go b/watch.go index cf653d6..83f2d8c 100644 --- a/watch.go +++ b/watch.go @@ -8,6 +8,7 @@ import ( "path/filepath" "time" "sync" + // "fmt" ) // FileWatcher monitors file-level events. @@ -45,6 +46,7 @@ func (fw *InotifyFileWatcher) BlockUntilExists() error { defer w.RemoveWatch(filepath.Dir(fw.Filename)) for { evt := <-w.Event + // fmt.Printf("block until exits (inotify) evt: %v\n", evt) if evt.Name == fw.Filename { break } @@ -53,7 +55,7 @@ func (fw *InotifyFileWatcher) BlockUntilExists() error { } // ChangeEvents returns a channel that gets updated when the file is ready to be read. -func (fw *InotifyFileWatcher) ChangeEvents(_ os.FileInfo) chan bool { +func (fw *InotifyFileWatcher) ChangeEvents(fi os.FileInfo) chan bool { w, err := fsnotify.NewWatcher() if err != nil { panic(err) @@ -65,6 +67,8 @@ func (fw *InotifyFileWatcher) ChangeEvents(_ os.FileInfo) chan bool { ch := make(chan bool) + fw.Size = fi.Size() + go func() { defer w.Close() defer w.RemoveWatch(fw.Filename) @@ -74,6 +78,7 @@ func (fw *InotifyFileWatcher) ChangeEvents(_ os.FileInfo) chan bool { prevSize := fw.Size evt := <-w.Event + // fmt.Printf("inotify change evt: %v\n", evt) switch { case evt.IsDelete(): fallthrough @@ -82,9 +87,14 @@ func (fw *InotifyFileWatcher) ChangeEvents(_ os.FileInfo) chan bool { return case evt.IsModify(): - fi, _ := os.Stat(fw.Filename) + fi, err := os.Stat(fw.Filename) + if err != nil { + // XXX: no panic here + panic(err) + } fw.Size = fi.Size() + // fmt.Printf("WATCH: prevSize=%d; fs.Size=%d\n", prevSize, fw.Size) if prevSize > 0 && prevSize > fw.Size { return } @@ -124,6 +134,7 @@ func (fw *PollingFileWatcher) BlockUntilExists() error { return err } time.Sleep(POLL_DURATION) + println("blocking..") } panic("unreachable") } From c90cd7b8db63e4be82d658fbcf9d677f029fcb96 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Tue, 28 May 2013 12:54:53 -0700 Subject: [PATCH 5/8] add truncation detection to PollingFileWatcher --- watch.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/watch.go b/watch.go index 83f2d8c..5aae9d7 100644 --- a/watch.go +++ b/watch.go @@ -8,7 +8,7 @@ import ( "path/filepath" "time" "sync" - // "fmt" + "fmt" ) // FileWatcher monitors file-level events. @@ -46,7 +46,7 @@ func (fw *InotifyFileWatcher) BlockUntilExists() error { defer w.RemoveWatch(filepath.Dir(fw.Filename)) for { evt := <-w.Event - // fmt.Printf("block until exits (inotify) evt: %v\n", evt) + fmt.Printf("block until exits (inotify) evt: %v\n", evt) if evt.Name == fw.Filename { break } @@ -78,7 +78,7 @@ func (fw *InotifyFileWatcher) ChangeEvents(fi os.FileInfo) chan bool { prevSize := fw.Size evt := <-w.Event - // fmt.Printf("inotify change evt: %v\n", evt) + fmt.Printf("inotify change evt: %v\n", evt) switch { case evt.IsDelete(): fallthrough @@ -94,7 +94,7 @@ func (fw *InotifyFileWatcher) ChangeEvents(fi os.FileInfo) chan bool { } fw.Size = fi.Size() - // fmt.Printf("WATCH: prevSize=%d; fs.Size=%d\n", prevSize, fw.Size) + fmt.Printf("WATCH(inotify): prevSize=%d; fs.Size=%d\n", prevSize, fw.Size) if prevSize > 0 && prevSize > fw.Size { return } @@ -154,8 +154,11 @@ func (fw *PollingFileWatcher) ChangeEvents(origFi os.FileInfo) chan bool { stop <- true }() } + + fw.Size = origFi.Size() go func() { + prevSize := fw.Size for { select { case <-stop: @@ -180,6 +183,14 @@ func (fw *PollingFileWatcher) ChangeEvents(origFi os.FileInfo) chan bool { continue } + // Was the file truncated? + fw.Size = fi.Size() + fmt.Printf("WATCH(poll): prevSize=%d; fs.Size=%d\n", prevSize, fw.Size) + if prevSize > 0 && prevSize > fw.Size { + once.Do(stopAndClose) + continue + } + // If the file was changed since last check, notify. modTime := fi.ModTime() if modTime != prevModTime { @@ -188,6 +199,8 @@ func (fw *PollingFileWatcher) ChangeEvents(origFi os.FileInfo) chan bool { case ch <- true: default: } + }else{ + fmt.Printf("polling; not modified: %v == %v\n", modTime, prevModTime) } } }() From fb37e0b7ca0687ac4302d35e2b3dd6506bd5e384 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Tue, 28 May 2013 12:55:11 -0700 Subject: [PATCH 6/8] use a smaller poll duration for faster test runs --- tail.go | 5 +++++ tail_test.go | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tail.go b/tail.go index 8e8a71a..e46bddd 100644 --- a/tail.go +++ b/tail.go @@ -102,6 +102,9 @@ func (tail *Tail) reopen() error { if err != nil { if os.IsNotExist(err) { log.Printf("Waiting for %s to appear...", tail.Filename) + // XXX: potential race condition here, as the file + // could have been created right after out IsNotExist + // check above. this will lead to blocking here forever. err := tail.watcher.BlockUntilExists() if err != nil { return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err) @@ -187,6 +190,7 @@ func (tail *Tail) tailFileSync() { if !ok { changes = nil // XXX: how to kill changes' goroutine? + log.Println("Changes channel is closed.") // File got deleted/renamed if tail.ReOpen { // TODO: no logging in a library? @@ -208,6 +212,7 @@ func (tail *Tail) tailFileSync() { } } case <-tail.Dying(): + log.Println("Dying..") tail.close() return } diff --git a/tail_test.go b/tail_test.go index 57cec46..b10ff9e 100644 --- a/tail_test.go +++ b/tail_test.go @@ -14,6 +14,7 @@ import ( ) func init() { + // Clear the temporary test directory err := os.RemoveAll(".test") if err != nil { panic(err) @@ -172,7 +173,7 @@ func _TestReSeek(_t *testing.T, poll bool) { // Delete after a reasonable delay, to give tail sufficient time // to read all lines. <-time.After(100 * time.Millisecond) - // XXX t.RemoveFile("test.txt") + t.RemoveFile("test.txt") println("Stopping...") tail.Stop() @@ -204,6 +205,10 @@ func NewTailTest(name string, t *testing.T) TailTest { if err != nil { tt.Fatal(err) } + + // Use a smaller poll duration for faster test runs. + POLL_DURATION = 25 * time.Millisecond + return tt } From 644891ebbca90b3f28512873b364fb3b9f5c5783 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Tue, 28 May 2013 13:27:56 -0700 Subject: [PATCH 7/8] BlockUntilExists should return immediately if the file already exists this fixes a potential race condition in the use of BlockUntilExists following a file existence check (as we do in tail.go:reopen). closes issue 5 --- tail.go | 8 ++------ tail_test.go | 1 + watch.go | 23 ++++++++++++++++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/tail.go b/tail.go index e46bddd..6e7882b 100644 --- a/tail.go +++ b/tail.go @@ -102,11 +102,7 @@ func (tail *Tail) reopen() error { if err != nil { if os.IsNotExist(err) { log.Printf("Waiting for %s to appear...", tail.Filename) - // XXX: potential race condition here, as the file - // could have been created right after out IsNotExist - // check above. this will lead to blocking here forever. - err := tail.watcher.BlockUntilExists() - if err != nil { + if err := tail.watcher.BlockUntilExists(); err != nil { return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err) } continue @@ -191,7 +187,7 @@ func (tail *Tail) tailFileSync() { changes = nil // XXX: how to kill changes' goroutine? log.Println("Changes channel is closed.") - // File got deleted/renamed + // File got deleted/renamed/truncated. if tail.ReOpen { // TODO: no logging in a library? log.Printf("Re-opening moved/deleted/truncated file %s ...", tail.Filename) diff --git a/tail_test.go b/tail_test.go index b10ff9e..ae8abb1 100644 --- a/tail_test.go +++ b/tail_test.go @@ -123,6 +123,7 @@ func _TestReOpen(_t *testing.T, poll bool) { <-time.After(100 * time.Millisecond) t.RemoveFile("test.txt") + println("Stopping (REOPEN)...") tail.Stop() } diff --git a/watch.go b/watch.go index 5aae9d7..ea47674 100644 --- a/watch.go +++ b/watch.go @@ -14,7 +14,7 @@ import ( // FileWatcher monitors file-level events. type FileWatcher interface { // BlockUntilExists blocks until the missing file comes into - // existence. If the file already exists, block until it is recreated. + // existence. If the file already exists, returns immediately. BlockUntilExists() error // ChangeEvents returns a channel of events corresponding to the @@ -34,19 +34,34 @@ func NewInotifyFileWatcher(filename string) *InotifyFileWatcher { } func (fw *InotifyFileWatcher) BlockUntilExists() error { + fmt.Println("BUE(inotify): creating watcher") w, err := fsnotify.NewWatcher() if err != nil { return err } defer w.Close() - err = w.WatchFlags(filepath.Dir(fw.Filename), fsnotify.FSN_CREATE) + + dirname := filepath.Dir(fw.Filename) + + // Watch for new files to be created in the parent directory. + err = w.WatchFlags(dirname, fsnotify.FSN_CREATE) if err != nil { return err } defer w.RemoveWatch(filepath.Dir(fw.Filename)) + + fmt.Println("BUE(inotify): does file exist now?") + // Do a real check now as the file might have been created before + // calling `WatchFlags` above. + if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) { + // file exists, or stat returned an error. + return err + } + + fmt.Printf("BUE(inotify): checking events (last: %v)\n", err) for { evt := <-w.Event - fmt.Printf("block until exits (inotify) evt: %v\n", evt) + fmt.Printf("BUE(inotify): got event: %v\n", evt) if evt.Name == fw.Filename { break } @@ -124,8 +139,6 @@ func NewPollingFileWatcher(filename string) *PollingFileWatcher { var POLL_DURATION time.Duration -// BlockUntilExists blocks until the file comes into existence. If the -// file already exists, then block until it is created again. func (fw *PollingFileWatcher) BlockUntilExists() error { for { if _, err := os.Stat(fw.Filename); err == nil { From 976fc15b81509a89c70ae89e828ad37a861b3b09 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Tue, 28 May 2013 13:53:19 -0700 Subject: [PATCH 8/8] update change log, gofmt and remove debug prints --- CHANGES.md | 2 ++ tail.go | 12 +++++------- tail_test.go | 14 ++++++-------- watch.go | 17 +++-------------- 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9880013..95c5f6f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # May, 2013 * Recognize deletions/renames when using polling file watcher (PR #1) +* Detect file truncation +* Fix potential race condition when reopening the file (issue 5) # Feb, 2013 diff --git a/tail.go b/tail.go index 6e7882b..c8647aa 100644 --- a/tail.go +++ b/tail.go @@ -13,8 +13,8 @@ import ( ) type Line struct { - Text string - Time time.Time + Text string + Time time.Time } // Tail configuration @@ -156,7 +156,7 @@ func (tail *Tail) tailFileSync() { for _, line := range partitionString(string(line), tail.MaxLineSize) { tail.Lines <- &Line{line, now} } - }else{ + } else { tail.Lines <- &Line{string(line), now} } } @@ -186,7 +186,6 @@ func (tail *Tail) tailFileSync() { if !ok { changes = nil // XXX: how to kill changes' goroutine? - log.Println("Changes channel is closed.") // File got deleted/renamed/truncated. if tail.ReOpen { // TODO: no logging in a library? @@ -199,7 +198,7 @@ func (tail *Tail) tailFileSync() { } log.Printf("Successfully reopened %s", tail.Filename) tail.reader = bufio.NewReader(tail.file) - + continue } else { log.Printf("Finishing because file has been moved/deleted: %s", tail.Filename) @@ -208,7 +207,6 @@ func (tail *Tail) tailFileSync() { } } case <-tail.Dying(): - log.Println("Dying..") tail.close() return } @@ -231,7 +229,7 @@ func partitionString(s string, chunkSize int) []string { panic("invalid chunkSize") } length := len(s) - chunks := 1 + length/chunkSize + chunks := 1 + length/chunkSize start := 0 end := chunkSize parts := make([]string, 0, chunks) diff --git a/tail_test.go b/tail_test.go index ae8abb1..cc1af62 100644 --- a/tail_test.go +++ b/tail_test.go @@ -85,7 +85,7 @@ func _TestReOpen(_t *testing.T, poll bool) { var name string if poll { name = "reopen-polling" - }else { + } else { name = "reopen-inotify" } t := NewTailTest(name, _t) @@ -93,7 +93,7 @@ func _TestReOpen(_t *testing.T, poll bool) { tail := t.StartTail( "test.txt", Config{Follow: true, ReOpen: true, Poll: poll, Location: -1}) - + go t.VerifyTailOutput(tail, []string{"hello", "world", "more", "data", "endofworld"}) // deletion must trigger reopen @@ -138,12 +138,11 @@ func TestReOpenPolling(_t *testing.T) { _TestReOpen(_t, true) } - func _TestReSeek(_t *testing.T, poll bool) { var name string if poll { name = "reseek-polling" - }else { + } else { name = "reseek-inotify" } t := NewTailTest(name, _t) @@ -151,7 +150,7 @@ func _TestReSeek(_t *testing.T, poll bool) { tail := t.StartTail( "test.txt", Config{Follow: true, ReOpen: true, Poll: poll, Location: -1}) - + go t.VerifyTailOutput(tail, []string{ "a really long string goes here", "hello", "world", "h311o", "w0r1d", "endofworld"}) @@ -176,7 +175,7 @@ func _TestReSeek(_t *testing.T, poll bool) { <-time.After(100 * time.Millisecond) t.RemoveFile("test.txt") - println("Stopping...") + println("Stopping (RESEEK)...") tail.Stop() } @@ -191,7 +190,6 @@ func TestReSeekPolling(_t *testing.T) { _TestReSeek(_t, true) } - // Test library type TailTest struct { @@ -275,7 +273,7 @@ func (t TailTest) VerifyTailOutput(tail *Tail, lines []string) { err := tail.Wait() if err != nil { t.Fatal("tail ended early with error: %v", err) - }else{ + } else { t.Fatalf("tail ended early; expecting more: %v", lines[idx:]) } } diff --git a/watch.go b/watch.go index ea47674..1d8c4c7 100644 --- a/watch.go +++ b/watch.go @@ -6,9 +6,8 @@ import ( "github.com/howeyc/fsnotify" "os" "path/filepath" - "time" "sync" - "fmt" + "time" ) // FileWatcher monitors file-level events. @@ -34,7 +33,6 @@ func NewInotifyFileWatcher(filename string) *InotifyFileWatcher { } func (fw *InotifyFileWatcher) BlockUntilExists() error { - fmt.Println("BUE(inotify): creating watcher") w, err := fsnotify.NewWatcher() if err != nil { return err @@ -50,7 +48,6 @@ func (fw *InotifyFileWatcher) BlockUntilExists() error { } defer w.RemoveWatch(filepath.Dir(fw.Filename)) - fmt.Println("BUE(inotify): does file exist now?") // Do a real check now as the file might have been created before // calling `WatchFlags` above. if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) { @@ -58,10 +55,8 @@ func (fw *InotifyFileWatcher) BlockUntilExists() error { return err } - fmt.Printf("BUE(inotify): checking events (last: %v)\n", err) for { evt := <-w.Event - fmt.Printf("BUE(inotify): got event: %v\n", evt) if evt.Name == fw.Filename { break } @@ -93,7 +88,6 @@ func (fw *InotifyFileWatcher) ChangeEvents(fi os.FileInfo) chan bool { prevSize := fw.Size evt := <-w.Event - fmt.Printf("inotify change evt: %v\n", evt) switch { case evt.IsDelete(): fallthrough @@ -109,7 +103,6 @@ func (fw *InotifyFileWatcher) ChangeEvents(fi os.FileInfo) chan bool { } fw.Size = fi.Size() - fmt.Printf("WATCH(inotify): prevSize=%d; fs.Size=%d\n", prevSize, fw.Size) if prevSize > 0 && prevSize > fw.Size { return } @@ -143,11 +136,10 @@ func (fw *PollingFileWatcher) BlockUntilExists() error { for { if _, err := os.Stat(fw.Filename); err == nil { return nil - }else if !os.IsNotExist(err) { + } else if !os.IsNotExist(err) { return err } time.Sleep(POLL_DURATION) - println("blocking..") } panic("unreachable") } @@ -169,7 +161,7 @@ func (fw *PollingFileWatcher) ChangeEvents(origFi os.FileInfo) chan bool { } fw.Size = origFi.Size() - + go func() { prevSize := fw.Size for { @@ -198,7 +190,6 @@ func (fw *PollingFileWatcher) ChangeEvents(origFi os.FileInfo) chan bool { // Was the file truncated? fw.Size = fi.Size() - fmt.Printf("WATCH(poll): prevSize=%d; fs.Size=%d\n", prevSize, fw.Size) if prevSize > 0 && prevSize > fw.Size { once.Do(stopAndClose) continue @@ -212,8 +203,6 @@ func (fw *PollingFileWatcher) ChangeEvents(origFi os.FileInfo) chan bool { case ch <- true: default: } - }else{ - fmt.Printf("polling; not modified: %v == %v\n", modTime, prevModTime) } } }()