fix: close stream adaptive gaps and switch notify to stario v0.1.1

- make stream fast path honor adaptive soft payload limits end-to-end
  - split oversized fast-stream payloads into sequential frames before batching
  - use adaptive soft cap when encoding stream batch payloads
  - move timeout-like error detection into production code for adaptive tx
  - tune notify FrameReader read size explicitly to avoid throughput regression
  - drop local stario replace and depend on released b612.me/stario v0.1.1
This commit is contained in:
2026-04-18 16:05:57 +08:00
parent 4f760f2807
commit f038a89771
76 changed files with 12656 additions and 906 deletions
+82 -18
View File
@@ -7,6 +7,7 @@ import (
"net"
"os"
"sync"
"sync/atomic"
"time"
)
@@ -93,7 +94,8 @@ type streamHandle struct {
runtimeScope string
id string
dataID uint64
outboundSeq uint64
fastPathVersion uint8
outboundSeq atomic.Uint64
channel StreamChannel
metadata StreamMetadata
sessionEpoch uint64
@@ -132,6 +134,9 @@ type streamHandle struct {
writeDeadlineOverride bool
readDeadlineNotify chan struct{}
writeDeadlineNotify chan struct{}
writeWaitSeq uint64
writeWaitCancel context.CancelFunc
writeWaitChanged chan struct{}
bytesRead int64
bytesWritten int64
readCalls int64
@@ -157,6 +162,7 @@ func newStreamHandle(parent context.Context, runtime *streamRuntime, runtimeScop
runtimeScope: runtimeScope,
id: req.StreamID,
dataID: req.DataID,
fastPathVersion: normalizeStreamFastPathVersion(req.FastPathVersion),
channel: normalizeStreamChannel(req.Channel),
metadata: cloneStreamMetadata(req.Metadata),
sessionEpoch: sessionEpoch,
@@ -224,13 +230,25 @@ func (s *streamHandle) dataIDSnapshot() uint64 {
}
func (s *streamHandle) nextOutboundDataSeq() uint64 {
return s.reserveOutboundDataSeqs(1)
}
func (s *streamHandle) reserveOutboundDataSeqs(count int) uint64 {
if s == nil {
return 0
}
s.mu.Lock()
defer s.mu.Unlock()
s.outboundSeq++
return s.outboundSeq
if count <= 0 {
count = 1
}
end := s.outboundSeq.Add(uint64(count))
return end - uint64(count) + 1
}
func (s *streamHandle) fastPathVersionSnapshot() uint8 {
if s == nil {
return streamFastPathVersionV1
}
return normalizeStreamFastPathVersion(s.fastPathVersion)
}
func (s *streamHandle) Channel() StreamChannel {
@@ -377,6 +395,7 @@ func (s *streamHandle) Write(p []byte) (int, error) {
sendDataFn := s.sendDataFn
chunkSize := s.chunkSize
writeTimeout := s.writeTimeout
writeDeadlineOverride := s.writeDeadlineOverride
streamCtx := s.ctx
runtime := s.runtime
s.mu.Unlock()
@@ -399,6 +418,20 @@ func (s *streamHandle) Write(p []byte) (int, error) {
end = len(p)
}
chunk := p[written:end]
if !writeDeadlineOverride && writeTimeout <= 0 {
if tryAcquireStreamOutboundBudget(runtime, len(chunk)) {
err := sendDataFn(streamCtx, s, chunk)
releaseStreamOutboundBudget(runtime, len(chunk))
if err != nil {
if written > 0 {
s.recordWrite(written, time.Now())
}
return written, s.normalizeWriteError(err)
}
written = end
continue
}
}
sendCtx, cancel, deadlineChanged, err := s.newWriteContext(streamCtx, writeTimeout)
if err != nil {
if written > 0 {
@@ -464,7 +497,15 @@ func (s *streamHandle) SetWriteDeadline(deadline time.Time) error {
s.writeDeadline = deadline
s.writeDeadlineOverride = true
signalStreamDeadlineChangeLocked(&s.writeDeadlineNotify)
waitCancel := s.writeWaitCancel
if s.writeWaitChanged != nil {
close(s.writeWaitChanged)
s.writeWaitChanged = nil
}
s.mu.Unlock()
if waitCancel != nil {
waitCancel()
}
return nil
}
@@ -535,7 +576,6 @@ func (s *streamHandle) newWriteContext(parent context.Context, writeTimeout time
}
s.mu.Lock()
deadline := s.effectiveWriteDeadlineLocked(time.Now(), writeTimeout)
deadlineNotify := s.writeDeadlineNotify
s.mu.Unlock()
if !deadline.IsZero() && !deadline.After(time.Now()) {
return nil, func() {}, nil, os.ErrDeadlineExceeded
@@ -548,19 +588,20 @@ func (s *streamHandle) newWriteContext(parent context.Context, writeTimeout time
baseCtx, baseCancel = context.WithCancel(parent)
}
changed := make(chan struct{})
done := make(chan struct{})
go func() {
defer close(done)
select {
case <-baseCtx.Done():
case <-deadlineNotify:
close(changed)
baseCancel()
}
}()
s.mu.Lock()
s.writeWaitSeq++
waitSeq := s.writeWaitSeq
s.writeWaitCancel = baseCancel
s.writeWaitChanged = changed
s.mu.Unlock()
cancel := func() {
baseCancel()
<-done
s.mu.Lock()
if s.writeWaitSeq == waitSeq {
s.writeWaitCancel = nil
s.writeWaitChanged = nil
}
s.mu.Unlock()
}
return baseCtx, cancel, changed, nil
}
@@ -814,7 +855,11 @@ func (s *streamHandle) pushChunkWithOwnership(chunk []byte, owned bool) error {
s.finalize()
return err
}
s.readQueue = append(s.readQueue, stored)
if len(s.readBuf) == 0 && len(s.readQueue) == 0 {
s.readBuf = stored
} else {
s.readQueue = append(s.readQueue, stored)
}
s.bufferedBytes += len(stored)
s.notifyReadableLocked()
s.mu.Unlock()
@@ -917,6 +962,10 @@ func (s *streamHandle) snapshot() StreamSnapshot {
snapshot.BindingCurrent = diag.BindingCurrent
snapshot.BindingReason = diag.BindingReason
snapshot.BindingError = diag.BindingError
snapshot.BindingBulkAdaptiveSoftPayloadBytes = diag.BindingBulkAdaptiveSoftPayloadBytes
snapshot.BindingStreamAdaptiveSoftPayloadBytes = diag.BindingStreamAdaptiveSoftPayloadBytes
snapshot.BindingStreamAdaptiveWaitThresholdBytes = diag.BindingStreamAdaptiveWaitThresholdBytes
snapshot.BindingStreamAdaptiveFlushDelay = diag.BindingStreamAdaptiveFlushDelay
snapshot.TransportAttached = diag.TransportAttached
snapshot.TransportHasRuntimeConn = diag.TransportHasRuntimeConn
snapshot.TransportCurrent = diag.TransportCurrent
@@ -1057,8 +1106,23 @@ func acquireStreamOutboundBudget(runtime *streamRuntime, ctx context.Context, si
return runtime.acquireOutbound(ctx, size)
}
func tryAcquireStreamOutboundBudget(runtime *streamRuntime, size int) bool {
if runtime == nil {
return true
}
return runtime.tryAcquireOutbound(size)
}
func releaseStreamOutboundBudget(runtime *streamRuntime, size int) {
if runtime == nil {
return
}
runtime.releaseOutbound(size)
}
func normalizeStreamOpenRequest(req StreamOpenRequest) StreamOpenRequest {
req.Channel = normalizeStreamChannel(req.Channel)
req.FastPathVersion = normalizeStreamFastPathVersion(req.FastPathVersion)
req.Metadata = cloneStreamMetadata(req.Metadata)
return req
}