From ac40992347344d2073a264f20fe5f3f89e01257d Mon Sep 17 00:00:00 2001 From: youdie006 Date: Mon, 28 Sep 2026 01:51:42 +0900 Subject: [PATCH] ReadFrom: keep the bytes returned together with a read error io.Reader may return n > 0 bytes along with a non-EOF error, and those bytes should be processed before the error. ReadFrom set the error and broke out without committing them or counting them in n, so io.Copy into a ring buffer from a source ending in io.ErrUnexpectedEOF under-reported what it took and dropped the last chunk. WriteTo already counts partial bytes on error (#20). --- ring_buffer.go | 6 ++++++ ring_buffer_test.go | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/ring_buffer.go b/ring_buffer.go index 1d31c28..b46e126 100644 --- a/ring_buffer.go +++ b/ring_buffer.go @@ -553,6 +553,12 @@ func (r *RingBuffer) ReadFrom(rd io.Reader) (n int64, err error) { continue } if rerr != nil && rerr != io.EOF { + // Keep the bytes that came with the error. + if nr > 0 { + r.w = (startW + nr) % r.size + r.isFull = r.r == r.w + n += int64(nr) + } err = r.setErr(rerr, true) r.mu.Unlock() break diff --git a/ring_buffer_test.go b/ring_buffer_test.go index dc64bd5..7a5c194 100644 --- a/ring_buffer_test.go +++ b/ring_buffer_test.go @@ -973,6 +973,7 @@ func (e *errormock) Read(p []byte) (n int, err error) { case len(p) > e.rleft: n = e.rleft + err = e.rerr default: n = len(p) @@ -1031,6 +1032,22 @@ func TestRingBuffer_ReadFrom_Error(t *testing.T) { } } +func TestRingBuffer_ReadFrom_ErrorKeepsData(t *testing.T) { + rb := New(16).SetBlocking(true) + tester := &errormock{rerr: io.ErrUnexpectedEOF, rleft: 5} + + copied, err := rb.ReadFrom(tester) + if err != io.ErrUnexpectedEOF { + t.Errorf("expect io.ErrUnexpectedEOF but got %v", err) + } + if copied != 5 { + t.Errorf("expect 5 bytes copied but got %d", copied) + } + if got := rb.Length(); got != 5 { + t.Errorf("expect 5 bytes buffered but got %d", got) + } +} + func TestRingBuffer_WriteTo_Error(t *testing.T) { const bufsize = 4 << 10