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:
parent
abb1479f04
commit
f69ef84e36
6
tail.go
6
tail.go
|
@ -290,11 +290,11 @@ func (tail *Tail) tailFileSync() {
|
||||||
// reopened if ReOpen is true. Truncated files are always reopened.
|
// reopened if ReOpen is true. Truncated files are always reopened.
|
||||||
func (tail *Tail) waitForChanges() error {
|
func (tail *Tail) waitForChanges() error {
|
||||||
if tail.changes == nil {
|
if tail.changes == nil {
|
||||||
st, err := tail.file.Stat()
|
pos, err := tail.file.Seek(0, os.SEEK_CUR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, st)
|
tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -340,7 +340,7 @@ func (tail *Tail) openReader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tail *Tail) seekEnd() error {
|
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 {
|
func (tail *Tail) seekTo(pos SeekInfo) error {
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
|
||||||
panic("unreachable")
|
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()
|
changes := NewFileChanges()
|
||||||
|
|
||||||
err := Watch(fw.Filename)
|
err := Watch(fw.Filename)
|
||||||
|
@ -66,7 +66,7 @@ func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, fi os.FileInfo) *FileCh
|
||||||
go changes.NotifyDeleted()
|
go changes.NotifyDeleted()
|
||||||
}
|
}
|
||||||
|
|
||||||
fw.Size = fi.Size()
|
fw.Size = pos
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer RemoveWatch(fw.Filename)
|
defer RemoveWatch(fw.Filename)
|
||||||
|
|
|
@ -40,14 +40,19 @@ func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
|
||||||
panic("unreachable")
|
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()
|
changes := NewFileChanges()
|
||||||
var prevModTime time.Time
|
var prevModTime time.Time
|
||||||
|
|
||||||
// XXX: use tomb.Tomb to cleanly manage these goroutines. replace
|
// XXX: use tomb.Tomb to cleanly manage these goroutines. replace
|
||||||
// the fatal (below) with tomb's Kill.
|
// 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() {
|
go func() {
|
||||||
defer changes.Close()
|
defer changes.Close()
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
package watch
|
package watch
|
||||||
|
|
||||||
import (
|
import "gopkg.in/tomb.v1"
|
||||||
"gopkg.in/tomb.v1"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileWatcher monitors file-level events.
|
// FileWatcher monitors file-level events.
|
||||||
type FileWatcher interface {
|
type FileWatcher interface {
|
||||||
|
@ -16,5 +13,7 @@ type FileWatcher interface {
|
||||||
// deletion, renames or truncations. Returned FileChanges group of
|
// deletion, renames or truncations. Returned FileChanges group of
|
||||||
// channels will be closed, thus become unusable, after a deletion
|
// channels will be closed, thus become unusable, after a deletion
|
||||||
// or truncation event.
|
// 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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue