Files
podman/libpod/logs/reversereader/reversereader.go
Paul Holzinger a55c2636de file logger: fix podman logs --tail with partial lines
There is a problem where our tail code does not handles correctly
partial log lines. This makes podman logs --tail output possibly
incorrect lines when k8s-file is used.

This manifests as flake in CI because partial lines are only sometimes
written, basically always when the output is flushed before writing a
newline.

For our code we must not count partial lines which was already done but
the important thing we must keep reading backwards until the next full
(F) line. This is because all partial (P) lines still must be added to
the full line. See the added tests for details on how the log file looks
like.

While fixing this, I rework the tail logic a bit, there is absolutely no
reason to read the lines in a separate goroutine just to pass the lines
back via channel. We can do this in the same routine.
The logic is very simple, read the lines backwards, append lines to
result and then at the end invert the result slice as tail must return
the lines in the correct order. This more efficient then having to
allocate two different slices or to prepend the line as this would
require a new allocation for each line.

Lastly the readFromLogFile() function wrote the lines back to the log
line channel in the same routine as the log lines we read, this was bad
and causes a deadlock when the returned lines are bigger than the
channel size. There is no reason to allocate a big channel size we can
just write the log lines in a different goroutine, in this case the main
routine were read the logs anyway.

A new system test and unit tests have been added to check corner cases.

Fixes #19545

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2023-08-09 14:48:01 +02:00

64 lines
1.6 KiB
Go

package reversereader
import (
"errors"
"fmt"
"io"
"os"
)
// ReverseReader structure for reading a file backwards
type ReverseReader struct {
reader *os.File
offset int64
readSize int64
}
// NewReverseReader returns a reader that reads from the end of a file
// rather than the beginning. It sets the readsize to pagesize and determines
// the first offset using modulus.
func NewReverseReader(reader *os.File) (*ReverseReader, error) {
// pagesize should be safe for memory use and file reads should be on page
// boundaries as well
pageSize := int64(os.Getpagesize())
stat, err := reader.Stat()
if err != nil {
return nil, err
}
// figure out the last page boundary
remainder := stat.Size() % pageSize
end, err := reader.Seek(0, 2)
if err != nil {
return nil, err
}
// set offset (starting position) to the last page boundary or
// zero if fits in one page
startOffset := end - remainder
if startOffset < 0 {
startOffset = 0
}
rr := ReverseReader{
reader: reader,
offset: startOffset,
readSize: pageSize,
}
return &rr, nil
}
// ReverseReader reads from a given offset to the previous offset and
// then sets the newoff set one pagesize less than the previous read.
func (r *ReverseReader) Read() (string, error) {
if r.offset < 0 {
return "", fmt.Errorf("at beginning of file: %w", io.EOF)
}
// Read from given offset
b := make([]byte, r.readSize)
n, err := r.reader.ReadAt(b, r.offset)
if err != nil && !errors.Is(err, io.EOF) {
return "", err
}
// Move the offset one pagesize up
r.offset -= r.readSize
return string(b[:n]), nil
}