Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SyncOpen config option #48

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type Config struct {
Location *SeekInfo // Tail from this location. If nil, start at the beginning of the file
ReOpen bool // Reopen recreated files (tail -F)
MustExist bool // Fail early if the file does not exist
SyncOpen bool // Do any seek operation before returning from TailFile.
Poll bool // Poll for file changes instead of using the default inotify
Pipe bool // The file is a named pipe (mkfifo)

Expand Down Expand Up @@ -146,11 +147,24 @@ func TailFile(filename string, config Config) (*Tail, error) {
t.watcher = watch.NewInotifyFileWatcher(filename)
}

if t.MustExist {
if t.MustExist || t.SyncOpen {
var err error
t.file, err = OpenFile(t.Filename)
if err != nil {
return nil, err
if t.MustExist {
if err != nil {
return nil, err
}
}
if t.SyncOpen && err == nil && t.Location != nil {
// Do any required seek operation before returning from TailFile.
//
// If the file didn't already exist, it's reasonable to assume that
// we shouldn't seek later when it opens, because its size when we
// first tried to open it was zero, so not seeking at all is fine.
_, err = t.file.Seek(t.Location.Offset, t.Location.Whence)
if err != nil {
return nil, err
}
}
}

Expand Down Expand Up @@ -283,7 +297,7 @@ func (tail *Tail) tailFileSync() {
}

// Seek to requested location on first open of the file.
if tail.Location != nil {
if tail.Location != nil && !tail.SyncOpen {
_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
if err != nil {
tail.Killf("Seek error on %s: %s", tail.Filename, err)
Expand Down
17 changes: 17 additions & 0 deletions tail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ func TestMustExist(t *testing.T) {
tail.Cleanup()
}

// This test is not very robust because its purpose is to test that a race
// condition doesn't exist. As such, the failure pattern of this test would
// be flaky failure, not necessarily easily reproduced failure.
//
// Running this same test *without* SyncOpen: true, alongside
// stress --hdd 8 (to make the filesystem very busy), will fail most
// of the time. (It fails significantly even without stress.)
func TestSeeksSynchronously(t *testing.T) {
tailTest, cleanup := NewTailTest("seeks-synchronously", t)
defer cleanup()
tailTest.CreateFile("test.txt", "hello\nworld\n")
tail := tailTest.StartTail("test.txt", Config{SyncOpen: true, Location: &SeekInfo{0, io.SeekEnd}})
go tailTest.VerifyTailOutput(tail, []string{"more", "data"}, false)
tailTest.AppendToFile("test.txt", "more\ndata\n")
tailTest.Cleanup(tail, true)
}

func TestWaitsForFileToExist(t *testing.T) {
tailTest, cleanup := NewTailTest("waits-for-file-to-exist", t)
defer cleanup()
Expand Down