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
This commit is contained in:
Sridhar Ratnakumar 2013-05-28 13:27:56 -07:00
parent fb37e0b7ca
commit 644891ebbc
3 changed files with 21 additions and 11 deletions

View File

@ -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)

View File

@ -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()
}

View File

@ -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 {