Skip to content

Commit abe92eb

Browse files
authored
Merge pull request #902 from google/decode-fix
fix: Correctly handle lines that cross buffer boundaries.
2 parents fa07f61 + b35d739 commit abe92eb

File tree

10 files changed

+271
-167
lines changed

10 files changed

+271
-167
lines changed

internal/tailer/logstream/base.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@
44
package logstream
55

66
import (
7-
"time"
8-
97
"github.com/google/mtail/internal/logline"
108
)
119

1210
type streamBase struct {
1311
sourcename string // human readable name of the logstream source
1412

1513
lines chan *logline.LogLine // outbound channel for lines
16-
17-
staleTimer *time.Timer // Expire the stream if no read in 24h.
1814
}
1915

2016
// Lines returns the output log line channel for this stream. The stream is

internal/tailer/logstream/decode.go

Lines changed: 0 additions & 68 deletions
This file was deleted.

internal/tailer/logstream/dgramstream.go

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
package logstream
55

66
import (
7-
"bytes"
87
"context"
98
"fmt"
109
"net"
1110
"sync"
12-
"time"
1311

1412
"github.com/golang/glog"
1513
"github.com/google/mtail/internal/logline"
@@ -55,8 +53,7 @@ func (ds *dgramStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wak
5553
return err
5654
}
5755
glog.V(2).Infof("stream(%s): opened new datagram socket %v", ds.sourcename, c)
58-
b := make([]byte, datagramReadBufferSize)
59-
partial := bytes.NewBufferString("")
56+
lr := NewLineReader(ds.sourcename, ds.lines, &dgramConn{c}, datagramReadBufferSize, ds.cancel)
6057
var total int
6158
wg.Add(1)
6259
go func() {
@@ -70,6 +67,7 @@ func (ds *dgramStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wak
7067
glog.Info(err)
7168
}
7269
logCloses.Add(ds.address, 1)
70+
lr.Finish(ctx)
7371
close(ds.lines)
7472
ds.cancel()
7573
}()
@@ -78,40 +76,27 @@ func (ds *dgramStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wak
7876
SetReadDeadlineOnDone(ctx, c)
7977

8078
for {
81-
n, _, err := c.ReadFrom(b)
79+
n, err := lr.ReadAndSend(ctx)
8280
glog.V(2).Infof("stream(%s): read %d bytes, err is %v", ds.sourcename, n, err)
8381

84-
if ds.staleTimer != nil {
85-
ds.staleTimer.Stop()
86-
}
87-
8882
// This is a test-only trick that says if we've already put this
8983
// logstream in graceful shutdown, then a zero-byte read is
9084
// equivalent to an "EOF" in connection and file oriented streams.
9185
if n == 0 {
9286
if oneShot {
9387
glog.V(2).Infof("stream(%s): exiting because zero byte read and one shot", ds.sourcename)
94-
if partial.Len() > 0 {
95-
ds.sendLine(ctx, partial)
96-
}
9788
return
9889
}
9990
select {
10091
case <-ctx.Done():
10192
glog.V(2).Infof("stream(%s): exiting because zero byte read after cancellation", ds.sourcename)
102-
if partial.Len() > 0 {
103-
ds.sendLine(ctx, partial)
104-
}
10593
return
10694
default:
10795
}
10896
}
10997

11098
if n > 0 {
11199
total += n
112-
//nolint:contextcheck
113-
ds.decodeAndSend(ctx, n, b[:n], partial)
114-
ds.staleTimer = time.AfterFunc(time.Hour*24, ds.cancel)
115100

116101
// No error implies more to read, so restart the loop.
117102
if err == nil && ctx.Err() == nil {
@@ -120,9 +105,6 @@ func (ds *dgramStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wak
120105
}
121106

122107
if IsExitableError(err) {
123-
if partial.Len() > 0 {
124-
ds.sendLine(ctx, partial)
125-
}
126108
glog.V(2).Infof("stream(%s): exiting, stream has error %s", ds.sourcename, err)
127109
return
128110
}
@@ -146,3 +128,14 @@ func (ds *dgramStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wak
146128
}()
147129
return nil
148130
}
131+
132+
// dgramConn wraps a PacketConn to add a Read method.
133+
type dgramConn struct {
134+
net.PacketConn
135+
}
136+
137+
// Read satisfies io.Reader
138+
func (d *dgramConn) Read(p []byte) (count int, err error) {
139+
count, _, err = d.ReadFrom(p)
140+
return
141+
}

internal/tailer/logstream/fifostream.go

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44
package logstream
55

66
import (
7-
"bytes"
87
"context"
98
"errors"
109
"io"
1110
"os"
1211
"sync"
1312
"syscall"
14-
"time"
1513

1614
"github.com/golang/glog"
1715
"github.com/google/mtail/internal/logline"
@@ -77,8 +75,7 @@ func (ps *fifoStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wake
7775
if err != nil {
7876
return err
7977
}
80-
b := make([]byte, defaultFifoReadBufferSize)
81-
partial := bytes.NewBufferString("")
78+
lr := NewLineReader(ps.sourcename, ps.lines, fd, defaultFifoReadBufferSize, ps.cancel)
8279
var total int
8380
wg.Add(1)
8481
go func() {
@@ -92,30 +89,17 @@ func (ps *fifoStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wake
9289
glog.Info(err)
9390
}
9491
logCloses.Add(ps.pathname, 1)
95-
if partial.Len() > 0 {
96-
ps.sendLine(ctx, partial)
97-
}
92+
lr.Finish(ctx)
9893
close(ps.lines)
9994
ps.cancel()
10095
}()
10196
SetReadDeadlineOnDone(ctx, fd)
10297

10398
for {
104-
// Because we've opened in nonblocking mode, this Read can return
105-
// straight away. If there are no writers, it'll return EOF (per
106-
// `pipe(7)` and `read(2)`.) This is expected when `mtail` is
107-
// starting at system init as the writer may not be ready yet.
108-
n, err := fd.Read(b)
109-
glog.V(2).Infof("stream(%s): read %d bytes, err is %v", ps.sourcename, n, err)
110-
111-
if ps.staleTimer != nil {
112-
ps.staleTimer.Stop()
113-
}
99+
n, err := lr.ReadAndSend(ctx)
114100

115101
if n > 0 {
116102
total += n
117-
ps.decodeAndSend(ctx, n, b[:n], partial)
118-
ps.staleTimer = time.AfterFunc(time.Hour*24, ps.cancel)
119103

120104
// No error implies there is more to read so restart the loop.
121105
if err == nil && ctx.Err() == nil {
@@ -134,6 +118,10 @@ func (ps *fifoStream) stream(ctx context.Context, wg *sync.WaitGroup, waker wake
134118

135119
// Test to see if we should exit.
136120
if IsExitableError(err) {
121+
// Because we've opened in nonblocking mode, this Read can return
122+
// straight away. If there are no writers, it'll return EOF (per
123+
// `pipe(7)` and `read(2)`.) This is expected when `mtail` is
124+
// starting at system init as the writer may not be ready yet.
137125
if !(errors.Is(err, io.EOF) && total == 0) {
138126
glog.V(2).Infof("stream(%s): exiting, stream has error %s", ps.sourcename, err)
139127
return

internal/tailer/logstream/fifostream_unix_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"golang.org/x/sys/unix"
2121
)
2222

23-
func TestPipeStreamReadCompletedBecauseClosed(t *testing.T) {
23+
func TestFifoStreamReadCompletedBecauseClosed(t *testing.T) {
2424
testutil.TimeoutTest(1*time.Second, func(t *testing.T) { //nolint:thelper
2525
var wg sync.WaitGroup
2626

@@ -63,7 +63,7 @@ func TestPipeStreamReadCompletedBecauseClosed(t *testing.T) {
6363
})(t)
6464
}
6565

66-
func TestPipeStreamReadCompletedBecauseCancel(t *testing.T) {
66+
func TestFifoStreamReadCompletedBecauseCancel(t *testing.T) {
6767
testutil.TimeoutTest(1*time.Second, func(t *testing.T) { // nolint:thelper
6868
var wg sync.WaitGroup
6969

@@ -98,7 +98,7 @@ func TestPipeStreamReadCompletedBecauseCancel(t *testing.T) {
9898
})(t)
9999
}
100100

101-
func TestPipeStreamReadURL(t *testing.T) {
101+
func TestFifoStreamReadURL(t *testing.T) {
102102
var wg sync.WaitGroup
103103

104104
tmpDir := testutil.TestTempDir(t)
@@ -143,7 +143,7 @@ func TestPipeStreamReadURL(t *testing.T) {
143143
cancel() // no-op for pipes
144144
}
145145

146-
func TestPipeStreamReadStdin(t *testing.T) {
146+
func TestFifoStreamReadStdin(t *testing.T) {
147147
var wg sync.WaitGroup
148148

149149
tmpDir := testutil.TestTempDir(t)

0 commit comments

Comments
 (0)