impl connection recovery
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The retained region is the one real cost stream resumption adds to the send
|
||||
// path: a chunk has to survive past the frame write, so it is copied. These
|
||||
// pin both halves of that claim — that the copy is the only cost, and that
|
||||
// disabling the feature removes it entirely rather than merely shrinking it.
|
||||
|
||||
func benchStream(resumable bool) *Stream {
|
||||
s := &Stream{resumable: resumable}
|
||||
return s
|
||||
}
|
||||
|
||||
// BenchmarkRetainChunk measures what emit adds over a bare frame write: the
|
||||
// trim-and-append into the retained region. Compare the two variants; the delta
|
||||
// is the per-byte copy the feature costs.
|
||||
func BenchmarkRetainChunk(b *testing.B) {
|
||||
chunk := make([]byte, DataChunkSize)
|
||||
window := int64(DefaultStreamWindow)
|
||||
|
||||
b.Run("resume-on", func(b *testing.B) {
|
||||
s := benchStream(true)
|
||||
b.SetBytes(int64(len(chunk)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Model the steady state: credit trails one window behind, so the
|
||||
// buffer trims about as fast as it grows and stays bounded.
|
||||
acked := s.un.end() - window
|
||||
if acked < 0 {
|
||||
acked = 0
|
||||
}
|
||||
s.un.advance(acked)
|
||||
s.un.append(chunk)
|
||||
}
|
||||
if got := int64(s.un.length()); got > window+int64(len(chunk)) {
|
||||
b.Fatalf("retained region grew past one window: %d", got)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("resume-off", func(b *testing.B) {
|
||||
s := benchStream(false)
|
||||
b.SetBytes(int64(len(chunk)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if s.resumable {
|
||||
s.un.advance(0)
|
||||
s.un.append(chunk)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestResumeDisabledAllocatesNothing pins the off switch at the level that
|
||||
// matters. It is easy for a feature flag to stop the wire behaviour while
|
||||
// leaving the bookkeeping running, which would keep the memory cost and the
|
||||
// per-byte copy for a user who explicitly turned it off — a partial revert that
|
||||
// nobody would notice.
|
||||
func TestResumeDisabledAllocatesNothing(t *testing.T) {
|
||||
chunk := make([]byte, DataChunkSize)
|
||||
s := benchStream(false)
|
||||
|
||||
allocs := testing.AllocsPerRun(1000, func() {
|
||||
if s.resumable {
|
||||
s.un.advance(0)
|
||||
s.un.append(chunk)
|
||||
}
|
||||
})
|
||||
if allocs != 0 {
|
||||
t.Fatalf("resume disabled still allocated %.1f times per send", allocs)
|
||||
}
|
||||
if s.un.buf != nil {
|
||||
t.Fatalf("resume disabled still allocated a retained region of %d bytes", cap(s.un.buf))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRetainedRegionStaysWithinWindow is the memory bound the design rests on:
|
||||
// flow control already caps outstanding bytes at one window, so the retained
|
||||
// region needs no cap of its own. If that ever stopped holding, a busy stream
|
||||
// would grow without limit and the hub would be the first to notice.
|
||||
func TestRetainedRegionStaysWithinWindow(t *testing.T) {
|
||||
const window = DefaultStreamWindow
|
||||
chunk := make([]byte, DataChunkSize)
|
||||
var u unackedBuf
|
||||
|
||||
for i := 0; i < 5000; i++ {
|
||||
// A sender may never have more than one window outstanding, which is
|
||||
// exactly what acquireSendWnd enforces before emit is ever reached.
|
||||
if u.length()+len(chunk) > window {
|
||||
u.advance(u.base() + int64(len(chunk)))
|
||||
}
|
||||
u.append(chunk)
|
||||
if u.length() > window {
|
||||
t.Fatalf("round %d: retained %d bytes for a %d-byte window", i, u.length(), window)
|
||||
}
|
||||
}
|
||||
if cap(u.buf) > 4*window {
|
||||
t.Fatalf("backing array grew to %d for a %d-byte window", cap(u.buf), window)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user