fix(notify): 根治低带宽控制面阻塞与传输写入卡死
- 为控制消息增加优先级、公平调度、队列字节预算和自适应批处理 - 支持可取消的写门等待,收紧 shared/dedicated bulk、stream 和 Reply 写入边界 - 修复 bulk reset/close、连接 handoff 和安全 profile 切换时序 - 保留旧取消与超时错误契约,新增阶段化 TransportSendError - 增加 ReplyCtx、ReplyObjCtx、写超时配置及黑洞连接和竞态回归测试
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user