fix(notify): 根治低带宽控制面阻塞与传输写入卡死

- 为控制消息增加优先级、公平调度、队列字节预算和自适应批处理
- 支持可取消的写门等待,收紧 shared/dedicated bulk、stream 和 Reply 写入边界
- 修复 bulk reset/close、连接 handoff 和安全 profile 切换时序
- 保留旧取消与超时错误契约,新增阶段化 TransportSendError
- 增加 ReplyCtx、ReplyObjCtx、写超时配置及黑洞连接和竞态回归测试
This commit is contained in:
2026-08-14 10:16:37 +08:00
parent 98ef9e7fcc
commit 0826e17063
45 changed files with 4231 additions and 293 deletions
+159
View File
@@ -22,6 +22,15 @@ const (
streamAdaptiveSoftPayloadMinSampleBytes = 64 * 1024
streamAdaptiveSoftPayloadGrowSuccesses = 8
controlAdaptiveSoftPayloadMinBytes = 256 * 1024
controlAdaptiveSoftPayloadFallbackBytes = 1 * 1024 * 1024
controlAdaptiveSoftPayloadStartBytes = 4 * 1024 * 1024
controlAdaptiveSoftPayloadMaxBytes = 16 * 1024 * 1024
controlAdaptiveSoftPayloadTargetFlush = 100 * time.Millisecond
controlAdaptiveSoftPayloadSlowFlush = 500 * time.Millisecond
controlAdaptiveSoftPayloadMinSampleBytes = 64 * 1024
controlAdaptiveSoftPayloadGrowSuccesses = 8
streamAdaptiveWaitThresholdMinBytes = 32 * 1024
streamAdaptiveFlushDelayMid = 25 * time.Microsecond
)
@@ -42,6 +51,16 @@ var streamAdaptiveSoftPayloadSteps = [...]int{
streamBatchMaxPayloadBytes,
}
var controlAdaptiveSoftPayloadSteps = [...]int{
256 * 1024,
512 * 1024,
1024 * 1024,
2 * 1024 * 1024,
controlAdaptiveSoftPayloadStartBytes,
8 * 1024 * 1024,
controlAdaptiveSoftPayloadMaxBytes,
}
type adaptiveTxState struct {
mu sync.Mutex
@@ -52,6 +71,24 @@ type adaptiveTxState struct {
streamSoftPayloadBytes int
streamGoodputBytesPerS float64
streamGrowStreak int
controlSoftPayloadBytes int
controlGoodputBytesPerS float64
controlGrowStreak int
}
func (b *transportBinding) controlAdaptiveSoftPayloadBytesSnapshot() int {
if b == nil {
return controlAdaptiveSoftPayloadFallbackBytes
}
return b.adaptiveTx.controlSoftPayloadBytesSnapshot()
}
func (b *transportBinding) observeControlAdaptivePayloadWrite(payloadBytes int, elapsed time.Duration, timeout time.Duration, err error) {
if b == nil {
return
}
b.adaptiveTx.observeControlPayloadWrite(payloadBytes, elapsed, timeout, err)
}
func (b *transportBinding) bulkAdaptiveSoftPayloadBytesSnapshot() int {
@@ -172,6 +209,73 @@ func (s *adaptiveTxState) streamSoftPayloadBytesSnapshot() int {
return s.streamSoftPayloadBytesLocked()
}
func (s *adaptiveTxState) controlSoftPayloadBytesSnapshot() int {
if s == nil {
return controlAdaptiveSoftPayloadStartBytes
}
s.mu.Lock()
defer s.mu.Unlock()
return s.controlSoftPayloadBytesLocked()
}
func (s *adaptiveTxState) observeControlPayloadWrite(payloadBytes int, elapsed time.Duration, timeout time.Duration, err error) {
if s == nil || payloadBytes <= 0 {
return
}
s.mu.Lock()
defer s.mu.Unlock()
current := s.controlSoftPayloadBytesLocked()
target, hasSample := s.observeControlGoodputLocked(payloadBytes, elapsed)
nearTimeout := timeout > 0 && elapsed >= (timeout*3)/4
if isTimeoutLikeError(err) || nearTimeout {
s.controlGrowStreak = 0
if hasSample && target < current {
s.controlSoftPayloadBytes = target
return
}
s.controlSoftPayloadBytes = previousControlAdaptiveSoftPayloadStep(current)
return
}
if err != nil {
s.controlGrowStreak = 0
return
}
if !hasSample {
return
}
if elapsed >= controlAdaptiveSoftPayloadSlowFlush {
s.controlGrowStreak = 0
if target < current {
s.controlSoftPayloadBytes = target
return
}
s.controlSoftPayloadBytes = previousControlAdaptiveSoftPayloadStep(current)
return
}
if target > current {
s.controlGrowStreak++
if s.controlGrowStreak >= controlAdaptiveSoftPayloadGrowSuccesses {
s.controlSoftPayloadBytes = nextControlAdaptiveSoftPayloadStep(current, target)
s.controlGrowStreak = 0
}
return
}
if target < current && elapsed >= controlAdaptiveSoftPayloadTargetFlush*2 {
s.controlSoftPayloadBytes = target
s.controlGrowStreak = 0
return
}
s.controlGrowStreak = 0
}
func (s *adaptiveTxState) controlSoftPayloadBytesLocked() int {
if s.controlSoftPayloadBytes == 0 {
s.controlSoftPayloadBytes = controlAdaptiveSoftPayloadStartBytes
}
return normalizeControlAdaptiveSoftPayloadBytes(s.controlSoftPayloadBytes)
}
func (s *adaptiveTxState) streamWaitThresholdBytesSnapshot() int {
if s == nil {
return streamBatchWaitThreshold
@@ -284,6 +388,24 @@ func (s *adaptiveTxState) observeStreamGoodputLocked(payloadBytes int, elapsed t
return normalizeStreamAdaptiveSoftPayloadBytes(target), true
}
func (s *adaptiveTxState) observeControlGoodputLocked(payloadBytes int, elapsed time.Duration) (int, bool) {
if payloadBytes < controlAdaptiveSoftPayloadMinSampleBytes || elapsed <= 0 {
return 0, false
}
sample := float64(payloadBytes) / elapsed.Seconds()
if sample <= 0 {
return 0, false
}
if s.controlGoodputBytesPerS <= 0 {
s.controlGoodputBytesPerS = sample
} else {
const alpha = 0.25
s.controlGoodputBytesPerS = s.controlGoodputBytesPerS*(1-alpha) + sample*alpha
}
target := int(s.controlGoodputBytesPerS * controlAdaptiveSoftPayloadTargetFlush.Seconds())
return normalizeControlAdaptiveSoftPayloadBytes(target), true
}
func normalizeBulkAdaptiveSoftPayloadBytes(size int) int {
if size <= bulkAdaptiveSoftPayloadMinBytes {
return bulkAdaptiveSoftPayloadMinBytes
@@ -358,6 +480,43 @@ func nextStreamAdaptiveSoftPayloadStep(current int, target int) int {
return streamAdaptiveSoftPayloadStartBytes
}
func normalizeControlAdaptiveSoftPayloadBytes(size int) int {
if size <= controlAdaptiveSoftPayloadMinBytes {
return controlAdaptiveSoftPayloadMinBytes
}
for _, step := range controlAdaptiveSoftPayloadSteps {
if size <= step {
return step
}
}
return controlAdaptiveSoftPayloadMaxBytes
}
func previousControlAdaptiveSoftPayloadStep(current int) int {
current = normalizeControlAdaptiveSoftPayloadBytes(current)
for index := len(controlAdaptiveSoftPayloadSteps) - 1; index >= 0; index-- {
step := controlAdaptiveSoftPayloadSteps[index]
if current > step {
return step
}
}
return controlAdaptiveSoftPayloadMinBytes
}
func nextControlAdaptiveSoftPayloadStep(current int, target int) int {
current = normalizeControlAdaptiveSoftPayloadBytes(current)
target = normalizeControlAdaptiveSoftPayloadBytes(target)
for _, step := range controlAdaptiveSoftPayloadSteps {
if step > current {
if step > target {
return target
}
return step
}
}
return controlAdaptiveSoftPayloadMaxBytes
}
func streamAdaptiveWaitThresholdBytesForSoftPayload(size int) int {
size = normalizeStreamAdaptiveSoftPayloadBytes(size)
threshold := size / 16