diff --git a/tail.go b/tail.go index cedbb23..877f849 100644 --- a/tail.go +++ b/tail.go @@ -4,6 +4,7 @@ package tail import ( "bufio" + "errors" "fmt" "github.com/ActiveState/tail/ratelimiter" "github.com/ActiveState/tail/util" @@ -136,6 +137,14 @@ func (tail *Tail) Stop() error { return tail.Wait() } +// StopAtEOF stops tailing as soon as the end of the file is reached. +func (tail *Tail) StopAtEOF() error { + tail.Kill(errStopAtEOF) + return tail.Wait() +} + +var errStopAtEOF = errors.New("tail: stop at eof") + func (tail *Tail) close() { close(tail.Lines) if tail.file != nil { @@ -209,6 +218,8 @@ func (tail *Tail) tailFileSync() { tail.openReader() + var stopAtEOF bool + // Read line by line. for { line, err := tail.readLine() @@ -228,14 +239,16 @@ func (tail *Tail) tailFileSync() { case <-tail.Dying(): return } - err = tail.seekEnd() - if err != nil { + if err := tail.seekEnd(); err != nil { tail.Kill(err) return } } + if stopAtEOF && err == io.EOF { + return + } } else if err == io.EOF { - if !tail.Follow { + if stopAtEOF || !tail.Follow { return } // When EOF is reached, wait for more data to become @@ -256,6 +269,10 @@ func (tail *Tail) tailFileSync() { select { case <-tail.Dying(): + if tail.Err() == errStopAtEOF { + stopAtEOF = true + continue + } return default: } diff --git a/tail_test.go b/tail_test.go index 0b9c5f9..f7abe37 100644 --- a/tail_test.go +++ b/tail_test.go @@ -54,6 +54,25 @@ func TestStop(t *testing.T) { Cleanup() } +func TestStopAtEOF(_t *testing.T) { + t := NewTailTest("maxlinesize", _t) + t.CreateFile("test.txt", "hello\nthere\nworld\n") + tail := t.StartTail("test.txt", Config{Follow: true, Location: nil}) + + // read "hello" + <-tail.Lines + + done := make(chan struct{}) + go func() { + <-time.After(100 * time.Millisecond) + t.VerifyTailOutput(tail, []string{"there", "world"}) + close(done) + }() + tail.StopAtEOF() + <-done + Cleanup() +} + func TestMaxLineSize(_t *testing.T) { t := NewTailTest("maxlinesize", _t) t.CreateFile("test.txt", "hello\nworld\nfin\nhe")