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
+165 -55
View File
@@ -3,15 +3,19 @@ package notify
import (
"context"
"sync"
"sync/atomic"
)
type streamFlowController struct {
mu sync.Mutex
queue []*streamFlowRequest
inFlightBytes int
inFlightChunks int
windowBytes int
maxChunks int
mu sync.Mutex
queue []*streamFlowRequest
inFlightBytes atomic.Int64
inFlightChunks atomic.Int64
windowBytes atomic.Int64
maxChunks atomic.Int64
waiters atomic.Int32
}
type streamFlowRequest struct {
@@ -22,10 +26,10 @@ type streamFlowRequest struct {
func newStreamFlowController(cfg streamConfig) *streamFlowController {
cfg = normalizeStreamConfig(cfg)
return &streamFlowController{
windowBytes: cfg.OutboundWindowBytes,
maxChunks: cfg.OutboundMaxInFlightChunks,
}
controller := &streamFlowController{}
controller.windowBytes.Store(int64(cfg.OutboundWindowBytes))
controller.maxChunks.Store(int64(cfg.OutboundMaxInFlightChunks))
return controller
}
func (c *streamFlowController) applyConfig(cfg streamConfig) {
@@ -33,9 +37,12 @@ func (c *streamFlowController) applyConfig(cfg streamConfig) {
return
}
cfg = normalizeStreamConfig(cfg)
c.windowBytes.Store(int64(cfg.OutboundWindowBytes))
c.maxChunks.Store(int64(cfg.OutboundMaxInFlightChunks))
if c.waiters.Load() == 0 {
return
}
c.mu.Lock()
c.windowBytes = cfg.OutboundWindowBytes
c.maxChunks = cfg.OutboundMaxInFlightChunks
c.drainLocked()
c.mu.Unlock()
}
@@ -47,58 +54,32 @@ func (c *streamFlowController) acquire(ctx context.Context, size int) (func(), e
if ctx == nil {
ctx = context.Background()
}
if c.tryAcquire(size) {
return c.releaseFunc(size), nil
}
req := &streamFlowRequest{
size: size,
ready: make(chan struct{}),
}
c.mu.Lock()
if c.tryAcquireLocked(size) {
c.mu.Unlock()
return c.releaseFunc(size), nil
}
c.queue = append(c.queue, req)
c.waiters.Add(1)
c.drainLocked()
c.mu.Unlock()
select {
case <-req.ready:
released := false
return func() {
c.mu.Lock()
if released {
c.mu.Unlock()
return
}
released = true
c.inFlightBytes -= size
if c.inFlightBytes < 0 {
c.inFlightBytes = 0
}
if c.inFlightChunks > 0 {
c.inFlightChunks--
}
c.drainLocked()
c.mu.Unlock()
}, nil
return c.releaseFunc(size), nil
case <-ctx.Done():
c.mu.Lock()
if req.admitted {
c.mu.Unlock()
released := false
return func() {
c.mu.Lock()
if released {
c.mu.Unlock()
return
}
released = true
c.inFlightBytes -= size
if c.inFlightBytes < 0 {
c.inFlightBytes = 0
}
if c.inFlightChunks > 0 {
c.inFlightChunks--
}
c.drainLocked()
c.mu.Unlock()
}, nil
return c.releaseFunc(size), nil
}
c.removeLocked(req)
c.drainLocked()
@@ -107,6 +88,128 @@ func (c *streamFlowController) acquire(ctx context.Context, size int) (func(), e
}
}
func (c *streamFlowController) tryAcquire(size int) bool {
if c == nil || size <= 0 {
return true
}
if c.waiters.Load() != 0 {
return false
}
return c.tryAcquireCAS(size)
}
func (c *streamFlowController) tryAcquireLocked(size int) bool {
if c == nil || size <= 0 {
return true
}
if len(c.queue) != 0 {
return false
}
return c.tryAcquireCAS(size)
}
func (c *streamFlowController) tryAcquireCAS(size int) bool {
if c == nil || size <= 0 {
return true
}
size64 := int64(size)
for {
window := c.windowBytes.Load()
maxChunks := c.maxChunks.Load()
inFlightBytes := c.inFlightBytes.Load()
inFlightChunks := c.inFlightChunks.Load()
if maxChunks > 0 && inFlightChunks >= maxChunks {
return false
}
if window > 0 && inFlightBytes+size64 > window {
if !(inFlightBytes == 0 && inFlightChunks == 0) {
return false
}
}
if !c.inFlightBytes.CompareAndSwap(inFlightBytes, inFlightBytes+size64) {
continue
}
if c.addChunksCAS(1, maxChunks) {
return true
}
c.subBytesCAS(size64)
return false
}
}
func (c *streamFlowController) addChunksCAS(delta int64, maxChunks int64) bool {
if c == nil || delta <= 0 {
return true
}
for {
current := c.inFlightChunks.Load()
if maxChunks > 0 && current+delta > maxChunks {
return false
}
if c.inFlightChunks.CompareAndSwap(current, current+delta) {
return true
}
}
}
func (c *streamFlowController) subBytesCAS(delta int64) {
if c == nil || delta <= 0 {
return
}
for {
current := c.inFlightBytes.Load()
next := current - delta
if next < 0 {
next = 0
}
if c.inFlightBytes.CompareAndSwap(current, next) {
return
}
}
}
func (c *streamFlowController) subChunksCAS(delta int64) {
if c == nil || delta <= 0 {
return
}
for {
current := c.inFlightChunks.Load()
next := current - delta
if next < 0 {
next = 0
}
if c.inFlightChunks.CompareAndSwap(current, next) {
return
}
}
}
func (c *streamFlowController) releaseFunc(size int) func() {
released := false
return func() {
if released {
return
}
released = true
c.release(size)
}
}
func (c *streamFlowController) release(size int) {
if c == nil || size <= 0 {
return
}
c.subBytesCAS(int64(size))
c.subChunksCAS(1)
if c.waiters.Load() == 0 {
return
}
c.mu.Lock()
c.drainLocked()
c.mu.Unlock()
}
func (c *streamFlowController) removeLocked(req *streamFlowRequest) {
if c == nil || req == nil {
return
@@ -118,6 +221,7 @@ func (c *streamFlowController) removeLocked(req *streamFlowRequest) {
copy(c.queue[i:], c.queue[i+1:])
c.queue[len(c.queue)-1] = nil
c.queue = c.queue[:len(c.queue)-1]
c.waiters.Add(-1)
return
}
}
@@ -132,18 +236,17 @@ func (c *streamFlowController) drainLocked() {
c.queue = c.queue[1:]
continue
}
if c.maxChunks > 0 && c.inFlightChunks >= c.maxChunks {
if !c.canAdmitLocked(req.size) {
return
}
if !c.canAdmitLocked(req.size) {
if !c.tryAcquireCAS(req.size) {
return
}
copy(c.queue[0:], c.queue[1:])
c.queue[len(c.queue)-1] = nil
c.queue = c.queue[:len(c.queue)-1]
c.waiters.Add(-1)
req.admitted = true
c.inFlightBytes += req.size
c.inFlightChunks++
close(req.ready)
}
}
@@ -155,11 +258,18 @@ func (c *streamFlowController) canAdmitLocked(size int) bool {
if size <= 0 {
return true
}
if c.windowBytes <= 0 {
window := c.windowBytes.Load()
chunks := c.inFlightChunks.Load()
bytes := c.inFlightBytes.Load()
maxChunks := c.maxChunks.Load()
if maxChunks > 0 && chunks >= maxChunks {
return false
}
if window <= 0 {
return true
}
if c.inFlightBytes+size <= c.windowBytes {
if bytes+int64(size) <= window {
return true
}
return c.inFlightBytes == 0 && c.inFlightChunks == 0
return bytes == 0 && chunks == 0
}