mirror of
https://github.com/soypat/lneto.git
synced 2026-09-10 16:49:37 +00:00
more defined semantics for low level Ring buffer indices
This commit is contained in:
+19
-10
@@ -15,11 +15,13 @@ type Ring struct {
|
|||||||
// Buf is used to store data written into Ring
|
// Buf is used to store data written into Ring
|
||||||
// with Write methods and then read out with Read methods.
|
// with Write methods and then read out with Read methods.
|
||||||
// The capacity of Buf is unused.
|
// The capacity of Buf is unused.
|
||||||
// There is no readable data when both Off and End are zero.
|
// There is no readable data when End==0.
|
||||||
Buf []byte
|
Buf []byte
|
||||||
// Start of readable data which indexes into Buf.
|
// Start of readable data which indexes into Buf.
|
||||||
|
// If Off==End and End!=0 the buffer is full and data begins at Off.
|
||||||
Off int
|
Off int
|
||||||
// End of readable data which indexes into Buf.
|
// End of readable data which indexes into Buf, not including byte at End index.
|
||||||
|
// If End==0 then the buffer is empty. If End==Off and End!=0 the buffer is full.
|
||||||
End int
|
End int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +113,7 @@ func (r *Ring) ReadAt(p []byte, off64 int64) (int, error) {
|
|||||||
return 0, io.ErrUnexpectedEOF
|
return 0, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
r2 := *r
|
r2 := *r
|
||||||
r2.Off = (r2.Off + off) % (r.Size())
|
r2.Off = r.addOff(r2.Off, off)
|
||||||
return r2.ReadPeek(p)
|
return r2.ReadPeek(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +179,6 @@ func (r *Ring) Free() int {
|
|||||||
if r.Off == 0 {
|
if r.Off == 0 {
|
||||||
return len(r.Buf) - r.End
|
return len(r.Buf) - r.End
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Off < r.End {
|
if r.Off < r.End {
|
||||||
// start off end len(buf)
|
// start off end len(buf)
|
||||||
// | sfree | used | efree |
|
// | sfree | used | efree |
|
||||||
@@ -199,15 +200,23 @@ func (r *Ring) midFree() int {
|
|||||||
|
|
||||||
// onReadEnd does some cleanup of [ring.off] and [ring.end] fields if possible for contiguous read performance benefits.
|
// onReadEnd does some cleanup of [ring.off] and [ring.end] fields if possible for contiguous read performance benefits.
|
||||||
func (r *Ring) onReadEnd() {
|
func (r *Ring) onReadEnd() {
|
||||||
if r.End == len(r.Buf) {
|
|
||||||
r.End = 0 // Wrap around.
|
|
||||||
}
|
|
||||||
if r.Off == len(r.Buf) {
|
if r.Off == len(r.Buf) {
|
||||||
r.Off = 0 // Wrap around.
|
if r.End == len(r.Buf) {
|
||||||
|
r.Reset()
|
||||||
|
} else {
|
||||||
|
r.Off = 0
|
||||||
|
}
|
||||||
|
} else if r.Off == r.End {
|
||||||
|
r.Reset()
|
||||||
}
|
}
|
||||||
if r.Off == r.End {
|
}
|
||||||
r.Reset() // We read everything, reset.
|
|
||||||
|
func (r *Ring) addOff(a, b int) int {
|
||||||
|
result := a + b
|
||||||
|
if result > len(r.Buf) {
|
||||||
|
result -= len(r.Buf)
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func max(a, b int) int {
|
func max(a, b int) int {
|
||||||
|
|||||||
+34
-3
@@ -18,6 +18,7 @@ func TestRing(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
// Case where data is contiguous and at start of buffer.
|
// Case where data is contiguous and at start of buffer.
|
||||||
var buf [bufSize]byte
|
var buf [bufSize]byte
|
||||||
n, err := fragmentReadInto(r, buf[:])
|
n, err := fragmentReadInto(r, buf[:])
|
||||||
@@ -27,14 +28,14 @@ func TestRing(t *testing.T) {
|
|||||||
if string(buf[:n]) != data {
|
if string(buf[:n]) != data {
|
||||||
t.Fatalf("got %q; want %q", buf[:n], data)
|
t.Fatalf("got %q; want %q", buf[:n], data)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
// Case where data overwrites end of buffer.
|
// Case where data overwrites end of buffer.
|
||||||
const overdata = "hello world"
|
const overdata = "hello world"
|
||||||
n, err = r.Write([]byte(overdata))
|
n, err = r.Write([]byte(overdata))
|
||||||
if err == nil || n > 0 {
|
if err == nil || n > 0 {
|
||||||
t.Fatal(err, n)
|
t.Fatal(err, n)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
// Set Random data in ring buffer and read it back.
|
// Set Random data in ring buffer and read it back.
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
n := rng.Intn(bufSize)
|
n := rng.Intn(bufSize)
|
||||||
@@ -50,6 +51,7 @@ func TestRing(t *testing.T) {
|
|||||||
if string(buf[:n]) != overdata[:n] {
|
if string(buf[:n]) != overdata[:n] {
|
||||||
t.Error("got", buf[:n], "want", overdata[:n])
|
t.Error("got", buf[:n], "want", overdata[:n])
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set random data and write some more and read it back.
|
// Set random data and write some more and read it back.
|
||||||
@@ -71,6 +73,7 @@ func TestRing(t *testing.T) {
|
|||||||
if ngot != nsecond {
|
if ngot != nsecond {
|
||||||
t.Errorf("%d did not write data correctly: got %d; want %d", i, ngot, nsecond)
|
t.Errorf("%d did not write data correctly: got %d; want %d", i, ngot, nsecond)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
buf = [bufSize]byte{}
|
buf = [bufSize]byte{}
|
||||||
// Case where data wraps around end of buffer.
|
// Case where data wraps around end of buffer.
|
||||||
n, err = r.Read(buf[:])
|
n, err = r.Read(buf[:])
|
||||||
@@ -84,6 +87,7 @@ func TestRing(t *testing.T) {
|
|||||||
if string(buf[:n]) != overdata[:n] {
|
if string(buf[:n]) != overdata[:n] {
|
||||||
t.Errorf("got %q; want %q", buf[:n], overdata[:n])
|
t.Errorf("got %q; want %q", buf[:n], overdata[:n])
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
var readback [bufSize]byte
|
var readback [bufSize]byte
|
||||||
@@ -114,6 +118,7 @@ func TestRing(t *testing.T) {
|
|||||||
} else if !bytes.Equal(readback[nfirst:nfirst+nsecond], []byte(data[:nsecond])) {
|
} else if !bytes.Equal(readback[nfirst:nfirst+nsecond], []byte(data[:nsecond])) {
|
||||||
t.Error("second section not match")
|
t.Error("second section not match")
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two-tap ReadAt to make sure pointer not advanced.
|
// Two-tap ReadAt to make sure pointer not advanced.
|
||||||
@@ -135,6 +140,7 @@ func TestRing(t *testing.T) {
|
|||||||
} else if len(second) > 0 && !bytes.Equal(gotSecond, second) {
|
} else if len(second) > 0 && !bytes.Equal(gotSecond, second) {
|
||||||
t.Errorf("second section not match got=%q want=%q", gotSecond, second)
|
t.Errorf("second section not match got=%q want=%q", gotSecond, second)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadDiscard test.
|
// ReadDiscard test.
|
||||||
@@ -151,6 +157,7 @@ func TestRing(t *testing.T) {
|
|||||||
if !bytes.Equal(readback[:n], content[discard:]) {
|
if !bytes.Equal(readback[:n], content[discard:]) {
|
||||||
t.Errorf("want data read %q, got %q", content[discard:], readback[:n])
|
t.Errorf("want data read %q, got %q", content[discard:], readback[:n])
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = r._string(0)
|
_ = r._string(0)
|
||||||
@@ -178,6 +185,7 @@ func TestRingWriteLimited(t *testing.T) {
|
|||||||
r := &Ring{
|
r := &Ring{
|
||||||
Buf: make([]byte, bufSize),
|
Buf: make([]byte, bufSize),
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
var data [bufSize]byte
|
var data [bufSize]byte
|
||||||
var wdata [bufSize]byte
|
var wdata [bufSize]byte
|
||||||
for i := 0; i < 10000; i++ {
|
for i := 0; i < 10000; i++ {
|
||||||
@@ -205,6 +213,7 @@ func TestRingWriteLimited(t *testing.T) {
|
|||||||
wantN = min(toWrite, len(r.Buf)-r.End+limOff)
|
wantN = min(toWrite, len(r.Buf)-r.End+limOff)
|
||||||
}
|
}
|
||||||
overwrite := toWrite > wantN
|
overwrite := toWrite > wantN
|
||||||
|
|
||||||
n, err := r.WriteLimited(wdata[:toWrite], limOff)
|
n, err := r.WriteLimited(wdata[:toWrite], limOff)
|
||||||
if !overwrite && err != nil {
|
if !overwrite && err != nil {
|
||||||
t.Errorf("limited write: %s", err)
|
t.Errorf("limited write: %s", err)
|
||||||
@@ -218,6 +227,7 @@ func TestRingWriteLimited(t *testing.T) {
|
|||||||
t.Fatalf("OVERWRITE pos=%d end=%d lim=%d", i, r.End, limOff)
|
t.Fatalf("OVERWRITE pos=%d end=%d lim=%d", i, r.End, limOff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +258,7 @@ func TestRing_findcrash(t *testing.T) {
|
|||||||
} else if expectFree != free {
|
} else if expectFree != free {
|
||||||
t.Fatal(i, "free not updated correctly", expectFree, free)
|
t.Fatal(i, "free not updated correctly", expectFree, free)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, &r)
|
||||||
}
|
}
|
||||||
buffered := r.Buffered()
|
buffered := r.Buffered()
|
||||||
if buffered < 0 {
|
if buffered < 0 {
|
||||||
@@ -264,6 +275,7 @@ func TestRing_findcrash(t *testing.T) {
|
|||||||
} else if buffered != expectBuffered {
|
} else if buffered != expectBuffered {
|
||||||
t.Fatal(i, "buffered not updated correctly", expectBuffered, buffered)
|
t.Fatal(i, "buffered not updated correctly", expectBuffered, buffered)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, &r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,13 +304,14 @@ func testRing1_loopback(t *testing.T, rng *rand.Rand, ringbuf, data, auxbuf []by
|
|||||||
if ngot != nsecond {
|
if ngot != nsecond {
|
||||||
t.Errorf("did not write data correctly: got %d; want %d", ngot, nsecond)
|
t.Errorf("did not write data correctly: got %d; want %d", ngot, nsecond)
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, &r)
|
||||||
// Case where data wraps around end of buffer.
|
// Case where data wraps around end of buffer.
|
||||||
n, err := r.Read(auxbuf[:])
|
n, err := r.Read(auxbuf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
testRingSanity(t, &r)
|
||||||
if n != nfirst+nsecond {
|
if n != nfirst+nsecond {
|
||||||
t.Errorf("got %d; want %d (%d+%d)", n, nfirst+nsecond, nfirst, nsecond)
|
t.Errorf("got %d; want %d (%d+%d)", n, nfirst+nsecond, nfirst, nsecond)
|
||||||
}
|
}
|
||||||
@@ -359,4 +372,22 @@ func setRingData(t *testing.T, r *Ring, offset int, data []byte) {
|
|||||||
}
|
}
|
||||||
r.End = end
|
r.End = end
|
||||||
r.Off = off
|
r.Off = off
|
||||||
|
testRingSanity(t, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRingSanity(t *testing.T, r *Ring) {
|
||||||
|
// t.Helper() // Really costly call. Avoid calling it every single function call.
|
||||||
|
buf := r.Buffered()
|
||||||
|
free := r.Free()
|
||||||
|
sz := r.Size()
|
||||||
|
if r.End == 0 && buf > 0 {
|
||||||
|
t.Helper()
|
||||||
|
t.Fatalf("want end=0 to encode no data, got off=%d end=%d => buffered=%d", r.Off, r.End, r.Buffered())
|
||||||
|
} else if sz != free+buf {
|
||||||
|
t.Helper()
|
||||||
|
t.Fatalf("want size=free+buffered, got %d=%d+%d", sz, free, buf)
|
||||||
|
} else if r.End != 0 && r.Off == r.End && buf != sz {
|
||||||
|
t.Helper()
|
||||||
|
t.Fatalf("want (off==end && end!=0) to encode full buffer, got off=%d end=%d show fill ration %d/%d", r.Off, r.End, buf, sz)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user