Fix race in the detection of truncation.

Before going into ChangeEvents(), the code was calling stat on the file
to know where it was at, which is incorrect as stat could return the new
file size post truncation.  Instead we now ask the file descriptor about
our current offset, so we can compare our offset to the file size to try
to detect truncation.

Truncation detection remains brittle, but this closes an annoying race
we frequently run into.
This commit is contained in:
Benoit Sigoure 2015-10-27 15:45:55 -07:00
parent abb1479f04
commit f69ef84e36
4 changed files with 16 additions and 12 deletions

View File

@ -290,11 +290,11 @@ func (tail *Tail) tailFileSync() {
// reopened if ReOpen is true. Truncated files are always reopened.
func (tail *Tail) waitForChanges() error {
if tail.changes == nil {
st, err := tail.file.Stat()
pos, err := tail.file.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, st)
tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, pos)
}
select {
@ -340,7 +340,7 @@ func (tail *Tail) openReader() {
}
func (tail *Tail) seekEnd() error {
return tail.seekTo(SeekInfo{Offset: 0, Whence: 2})
return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END})
}
func (tail *Tail) seekTo(pos SeekInfo) error {

View File

@ -58,7 +58,7 @@ func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
panic("unreachable")
}
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, fi os.FileInfo) *FileChanges {
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) *FileChanges {
changes := NewFileChanges()
err := Watch(fw.Filename)
@ -66,7 +66,7 @@ func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, fi os.FileInfo) *FileCh
go changes.NotifyDeleted()
}
fw.Size = fi.Size()
fw.Size = pos
go func() {
defer RemoveWatch(fw.Filename)

View File

@ -40,14 +40,19 @@ func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
panic("unreachable")
}
func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, origFi os.FileInfo) *FileChanges {
func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) *FileChanges {
changes := NewFileChanges()
var prevModTime time.Time
// XXX: use tomb.Tomb to cleanly manage these goroutines. replace
// the fatal (below) with tomb's Kill.
fw.Size = origFi.Size()
fw.Size = pos
origFi, err := os.Stat(fw.Filename)
if err != nil {
changes.NotifyDeleted()
return changes
}
go func() {
defer changes.Close()

View File

@ -2,10 +2,7 @@
package watch
import (
"gopkg.in/tomb.v1"
"os"
)
import "gopkg.in/tomb.v1"
// FileWatcher monitors file-level events.
type FileWatcher interface {
@ -16,5 +13,7 @@ type FileWatcher interface {
// deletion, renames or truncations. Returned FileChanges group of
// channels will be closed, thus become unusable, after a deletion
// or truncation event.
ChangeEvents(*tomb.Tomb, os.FileInfo) *FileChanges
// In order to properly report truncations, ChangeEvents requires
// the caller to pass their current offset in the file.
ChangeEvents(*tomb.Tomb, int64) *FileChanges
}