Reports refused while report files cannot be written (closes #123)
check / check (push) Waiting to run
check / check (push) Waiting to run
A failed write of a report file used to drop its reports, and POST /api/v1/reports kept answering 200 for as long as writes failed. Now a failed write puts its reports back in the buffer, still counted against DATA_DIR_MAX_BYTES, and Append refuses every report, which the handler answers 500, until a write succeeds. The minute flush tries the held reports again, so storing resumes with no restart once writing works, and the buffer takes nothing new while writes fail. Model: opus-5-5
This commit is contained in:
@@ -91,6 +91,11 @@ type Buffer struct {
|
||||
// of the report files in dataDir, plus the reports waiting to
|
||||
// be written to one at their uncompressed size.
|
||||
usedBytes int64
|
||||
// writeErr is the error of the last write of a report file to
|
||||
// finish, nil if it succeeded or none has. While it is set, Append
|
||||
// refuses reports, and the reports of that write wait in buf for
|
||||
// the next flush.
|
||||
writeErr error
|
||||
}
|
||||
|
||||
// New creates a Buffer and registers lifecycle hooks to
|
||||
@@ -167,11 +172,12 @@ func New(
|
||||
}
|
||||
|
||||
// Append marshals v as a single JSON line and appends it to
|
||||
// the buffer. If the line would take usedBytes past maxBytes, the
|
||||
// oldest report files are deleted to make room; it stores nothing
|
||||
// and returns ErrFull if that cannot make room. If the buffer
|
||||
// reaches the size threshold, it is drained and written to disk
|
||||
// asynchronously.
|
||||
// the buffer. It stores nothing and returns an error if the last
|
||||
// write of a report file failed, until a write succeeds again. If the
|
||||
// line would take usedBytes past maxBytes, the oldest report files are
|
||||
// deleted to make room; it stores nothing and returns ErrFull if that
|
||||
// cannot make room. If the buffer reaches the size threshold, it is
|
||||
// drained and written to disk asynchronously.
|
||||
func (b *Buffer) Append(v any) error {
|
||||
line, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
@@ -182,6 +188,13 @@ func (b *Buffer) Append(v any) error {
|
||||
|
||||
b.mu.Lock()
|
||||
|
||||
if b.writeErr != nil {
|
||||
err = fmt.Errorf("report files cannot be written: %w", b.writeErr)
|
||||
b.mu.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
b.deleteOldestFiles(lineBytes)
|
||||
|
||||
if b.usedBytes+lineBytes > b.maxBytes {
|
||||
@@ -291,7 +304,8 @@ func (b *Buffer) drainBuf() []byte {
|
||||
}
|
||||
|
||||
// writeFile writes data, reports drained from the buffer, to a new
|
||||
// timestamped zstd-compressed JSONL file in the data directory.
|
||||
// timestamped zstd-compressed JSONL file in the data directory. If the
|
||||
// write fails, it puts the reports back in the buffer.
|
||||
func (b *Buffer) writeFile(data []byte) error {
|
||||
// The timestamp comes first, so the names sort by time; the number
|
||||
// after it tells apart files named in the same millisecond.
|
||||
@@ -300,19 +314,27 @@ func (b *Buffer) writeFile(data []byte) error {
|
||||
|
||||
size, err := b.createFile(filepath.Join(b.dataDir, name), data)
|
||||
|
||||
// The reports no longer wait to be written, so they stop counting
|
||||
// at their uncompressed size. If the write failed they are lost;
|
||||
// otherwise they count as the file, which from here on may be
|
||||
// deleted to make room.
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
b.usedBytes -= int64(len(data))
|
||||
b.writeErr = err
|
||||
|
||||
if err != nil {
|
||||
// The reports go back in front of any appended since, for the
|
||||
// next flush to write, and still count at their uncompressed
|
||||
// size.
|
||||
appended := bytes.Clone(b.buf.Bytes())
|
||||
b.buf.Reset()
|
||||
b.buf.Write(data)
|
||||
b.buf.Write(appended)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// The reports no longer wait to be written, so they stop counting
|
||||
// at their uncompressed size and count as the file, which from here
|
||||
// on may be deleted to make room.
|
||||
b.usedBytes -= int64(len(data))
|
||||
b.usedBytes += size
|
||||
b.filesBytes += size
|
||||
|
||||
|
||||
Reference in New Issue
Block a user