2019-05-28 16:31:35 +02:00
|
|
|
package common
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2021-06-14 11:15:30 +02:00
|
|
|
"time"
|
2019-05-28 16:31:35 +02:00
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
// The maximum size of a single line returned by ReadLinesBlock.
|
|
|
|
const maxLineSize = 256 * 1024
|
|
|
|
|
|
|
|
// Default size in bytes of a single block returned by ReadLinesBlock.
|
|
|
|
const defaultBlockSize = 64 * 1024
|
|
|
|
|
|
|
|
// ReadLinesBlock reads a block of lines delimited by '\n' from tailBuf and r into dstBuf.
|
|
|
|
//
|
|
|
|
// Trailing chars after the last newline are put into tailBuf.
|
|
|
|
//
|
|
|
|
// Returns (dstBuf, tailBuf).
|
2020-08-14 19:13:15 +02:00
|
|
|
//
|
|
|
|
// It is expected that read timeout on r exceeds 1 second.
|
2019-05-28 16:31:35 +02:00
|
|
|
func ReadLinesBlock(r io.Reader, dstBuf, tailBuf []byte) ([]byte, []byte, error) {
|
2019-12-09 19:58:19 +01:00
|
|
|
return ReadLinesBlockExt(r, dstBuf, tailBuf, maxLineSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadLinesBlockExt reads a block of lines delimited by '\n' from tailBuf and r into dstBuf.
|
|
|
|
//
|
|
|
|
// Trailing chars after the last newline are put into tailBuf.
|
|
|
|
//
|
|
|
|
// Returns (dstBuf, tailBuf).
|
|
|
|
//
|
|
|
|
// maxLineLen limits the maximum length of a single line.
|
2020-08-14 19:13:15 +02:00
|
|
|
//
|
|
|
|
// It is expected that read timeout on r exceeds 1 second.
|
2019-12-09 19:58:19 +01:00
|
|
|
func ReadLinesBlockExt(r io.Reader, dstBuf, tailBuf []byte, maxLineLen int) ([]byte, []byte, error) {
|
2021-06-14 11:25:43 +02:00
|
|
|
startTime := time.Now()
|
2019-05-28 16:31:35 +02:00
|
|
|
if cap(dstBuf) < defaultBlockSize {
|
|
|
|
dstBuf = bytesutil.Resize(dstBuf, defaultBlockSize)
|
|
|
|
}
|
|
|
|
dstBuf = append(dstBuf[:0], tailBuf...)
|
2019-06-26 01:51:54 +02:00
|
|
|
tailBuf = tailBuf[:0]
|
2019-05-28 16:31:35 +02:00
|
|
|
again:
|
|
|
|
n, err := r.Read(dstBuf[len(dstBuf):cap(dstBuf)])
|
|
|
|
// Check for error only if zero bytes read from r, i.e. no forward progress made.
|
|
|
|
// Otherwise process the read data.
|
|
|
|
if n == 0 {
|
|
|
|
if err == nil {
|
|
|
|
return dstBuf, tailBuf, fmt.Errorf("no forward progress made")
|
|
|
|
}
|
2019-06-07 22:36:58 +02:00
|
|
|
if err == io.EOF && len(dstBuf) > 0 {
|
2019-06-07 22:17:57 +02:00
|
|
|
// Missing newline in the end of stream. This is OK,
|
2019-06-26 01:51:54 +02:00
|
|
|
// so suppress io.EOF for now. It will be returned during the next
|
2019-06-07 22:17:57 +02:00
|
|
|
// call to ReadLinesBlock.
|
|
|
|
// This fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/60 .
|
|
|
|
return dstBuf, tailBuf, nil
|
|
|
|
}
|
2021-06-14 11:15:30 +02:00
|
|
|
if err != io.EOF {
|
2021-06-14 11:25:43 +02:00
|
|
|
err = fmt.Errorf("cannot read a block of data in %.3fs: %w", time.Since(startTime).Seconds(), err)
|
2021-06-14 11:15:30 +02:00
|
|
|
}
|
2019-05-28 16:31:35 +02:00
|
|
|
return dstBuf, tailBuf, err
|
|
|
|
}
|
|
|
|
dstBuf = dstBuf[:len(dstBuf)+n]
|
|
|
|
|
|
|
|
// Search for the last newline in dstBuf and put the rest into tailBuf.
|
|
|
|
nn := bytes.LastIndexByte(dstBuf[len(dstBuf)-n:], '\n')
|
|
|
|
if nn < 0 {
|
|
|
|
// Didn't found at least a single line.
|
2019-12-09 19:58:19 +01:00
|
|
|
if len(dstBuf) > maxLineLen {
|
|
|
|
return dstBuf, tailBuf, fmt.Errorf("too long line: more than %d bytes", maxLineLen)
|
2019-05-28 16:31:35 +02:00
|
|
|
}
|
|
|
|
if cap(dstBuf) < 2*len(dstBuf) {
|
|
|
|
// Increase dsbBuf capacity, so more data could be read into it.
|
|
|
|
dstBufLen := len(dstBuf)
|
|
|
|
dstBuf = bytesutil.Resize(dstBuf, 2*cap(dstBuf))
|
|
|
|
dstBuf = dstBuf[:dstBufLen]
|
|
|
|
}
|
|
|
|
goto again
|
|
|
|
}
|
|
|
|
|
|
|
|
// Found at least a single line. Return it.
|
|
|
|
nn += len(dstBuf) - n
|
|
|
|
tailBuf = append(tailBuf[:0], dstBuf[nn+1:]...)
|
|
|
|
dstBuf = dstBuf[:nn]
|
|
|
|
return dstBuf, tailBuf, nil
|
|
|
|
}
|