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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user