Skip to content

Commit ad4e60e

Browse files
committed
StopAtEOF: keep sending lines until EOF
When a StopAtEOF() is called the code should continue to send all lines to the Lines channel. The issue here is if the caller is not ready to receive a new line the code blocks as it is using a unbuffered channel. However <-tail.Dying() would return in this case so the line was skipped. This means that the caller did not get all lines until EOF. Now we still want to skip in case any other reason for kill was given therefore add special logic to only not read the Dying channel on the EOF case. The one downside is that StopAtEOF() could block forever if the caller never reads new Lines but this seems logical to me. If the caller wants to wait for EOF but never reads remaining Lines this would be a bug on their end. Fixes nxadm#37 Signed-off-by: Paul Holzinger <[email protected]>
1 parent ba755e4 commit ad4e60e

File tree

2 files changed

+49
-8
lines changed

2 files changed

+49
-8
lines changed

tail.go

+19-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
// Copyright (c) 2015 HPE Software Inc. All rights reserved.
33
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
44

5-
//nxadm/tail provides a Go library that emulates the features of the BSD `tail`
6-
//program. The library comes with full support for truncation/move detection as
7-
//it is designed to work with log rotation tools. The library works on all
8-
//operating systems supported by Go, including POSIX systems like Linux and
9-
//*BSD, and MS Windows. Go 1.9 is the oldest compiler release supported.
5+
// nxadm/tail provides a Go library that emulates the features of the BSD `tail`
6+
// program. The library comes with full support for truncation/move detection as
7+
// it is designed to work with log rotation tools. The library works on all
8+
// operating systems supported by Go, including POSIX systems like Linux and
9+
// *BSD, and MS Windows. Go 1.9 is the oldest compiler release supported.
1010
package tail
1111

1212
import (
@@ -450,12 +450,25 @@ func (tail *Tail) sendLine(line string) bool {
450450
lines = util.PartitionString(line, tail.MaxLineSize)
451451
}
452452

453+
// This is a bit weird here, when a users requests stopAtEof we
454+
// must keep sending all lines however <-tail.Dying() will return
455+
// immediately at this point so the select below may not have
456+
// chance to send the line if the reader side has is not yet ready.
457+
// But if StopAtEOF was not set and it is a "normal" Kill then we
458+
// should exit right away still thus the special logic here.
459+
earlyExitChan := tail.Dying()
460+
if tail.Err() == errStopAtEOF {
461+
// Note that receive from a nil channel blocks forever so
462+
// below we know it can only take the tail.Lines case.
463+
earlyExitChan = nil
464+
}
465+
453466
for _, line := range lines {
454467
tail.lineNum++
455468
offset, _ := tail.Tell()
456469
select {
457470
case tail.Lines <- &Line{line, tail.lineNum, SeekInfo{Offset: offset}, now, nil}:
458-
case <-tail.Dying():
471+
case <-earlyExitChan:
459472
return true
460473
}
461474
}

tail_test.go

+30-2
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,26 @@ func TestIncompleteLinesWithoutFollow(t *testing.T) {
622622
tail.Cleanup()
623623
}
624624

625+
func TestFollowUntilEof(t *testing.T) {
626+
tailTest, cleanup := NewTailTest("incomplete-lines-no-follow", t)
627+
defer cleanup()
628+
filename := "test.txt"
629+
config := Config{
630+
Follow: false,
631+
}
632+
tailTest.CreateFile(filename, "hello\nworld\n")
633+
tail := tailTest.StartTail(filename, config)
634+
635+
// StopAtEOF blocks until the read is done and in order to do so
636+
// we have to drain the lines channel first which ReadLinesWithError does.
637+
go tail.StopAtEOF()
638+
tailTest.ReadLinesWithError(tail, []string{"hello", "world"}, false, errStopAtEOF)
639+
640+
tailTest.RemoveFile(filename)
641+
tail.Stop()
642+
tail.Cleanup()
643+
}
644+
625645
func reSeek(t *testing.T, poll bool) {
626646
var name string
627647
if poll {
@@ -765,6 +785,14 @@ func (t TailTest) VerifyTailOutputUsingCursor(tail *Tail, lines []string, expect
765785
}
766786

767787
func (t TailTest) ReadLines(tail *Tail, lines []string, useCursor bool) {
788+
t.readLines(tail, lines, useCursor, nil)
789+
}
790+
791+
func (t TailTest) ReadLinesWithError(tail *Tail, lines []string, useCursor bool, err error) {
792+
t.readLines(tail, lines, useCursor, err)
793+
}
794+
795+
func (t TailTest) readLines(tail *Tail, lines []string, useCursor bool, expectErr error) {
768796
cursor := 1
769797

770798
for _, line := range lines {
@@ -773,8 +801,8 @@ func (t TailTest) ReadLines(tail *Tail, lines []string, useCursor bool) {
773801
if !ok {
774802
// tail.Lines is closed and empty.
775803
err := tail.Err()
776-
if err != nil {
777-
t.Fatalf("tail ended with error: %v", err)
804+
if err != expectErr {
805+
t.Fatalf("tail ended with unexpected error: %v", err)
778806
}
779807
t.Fatalf("tail ended early; expecting more: %v", lines[cursor:])
780808
}

0 commit comments

Comments
 (0)