fix(notify): 修复传输生命周期竞态,完善背压与协议边界

- 完善 stream/bulk DataID 分配、预留和双向命名空间,修复并发打开及 dedicated/shared 回退时的 ID 冲突
- 将收发、回复、恢复任务和 sidecar 绑定原始会话与物理连接,防止重连后的旧消息误操作新连接
- 加强 close/reset 身份校验及实例移除检查,修复 dedicated attach 失败、通道引用和资源回收竞态
- 收紧批量发送器停止准入,确保在途入队完成后统一清理请求、缓冲区和等待者
- 修复 record 满队列死锁、取消时序号消耗及关闭竞态,确保关闭有界并返回真实错误
- 增加协商式 record 逻辑半关闭,保留反向 ACK;通过 reset 传递 RecordFailure,避免背压掩盖原始失败原因
- 补齐帧长度、批次数量、序号溢出和未确认窗口校验,提前拒绝超限数据并按字节预算拆批
- 为入站分发增加全局及单连接的条数、字节预算和阻塞背压,关闭时唤醒等待者,消除正常断连日志噪音
- 完善 bulk 窗口释放失败处理与传输诊断,补充并发、重连、背压、协议边界及真实 TCP 回归覆盖
This commit is contained in:
2026-09-23 15:33:17 +08:00
parent 0826e17063
commit 1f2e74acca
79 changed files with 9190 additions and 1013 deletions
+86 -19
View File
@@ -46,11 +46,14 @@ type streamBatchSender struct {
stopCh chan struct{}
doneCh chan struct{}
stopOnce sync.Once
flushMu sync.Mutex
queued atomic.Int64
errMu sync.Mutex
err error
stopOnce sync.Once
admissionMu sync.Mutex
admitting sync.WaitGroup
admissionClosed bool
flushMu sync.Mutex
queued atomic.Int64
errMu sync.Mutex
err error
}
func newStreamBatchSender(binding *transportBinding, codec streamBatchCodec, writeTimeoutProvider func() time.Duration) *streamBatchSender {
@@ -146,14 +149,12 @@ func (s *streamBatchSender) submitRequest(req streamBatchRequest) error {
}
}
s.queued.Add(1)
select {
case <-req.ctx.Done():
s.queued.Add(-1)
return normalizeStreamDeadlineError(req.ctx.Err())
case <-s.stopCh:
if !s.enqueue(req) {
s.queued.Add(-1)
if err := req.ctx.Err(); err != nil {
return normalizeStreamDeadlineError(err)
}
return s.stoppedErr()
case s.reqCh <- req:
}
select {
case err := <-req.done:
@@ -210,7 +211,11 @@ func (s *streamBatchSender) tryDirectSubmit(req streamBatchRequest) (bool, error
return true, err
}
if err := s.flush([]streamBatchRequest{req}); err != nil {
s.setErr(err)
if isBatchSenderQueueWaitError(err) {
return true, err
}
s.markFailed(err)
s.waitAdmissions()
s.failPending(err)
return true, err
}
@@ -240,7 +245,10 @@ func (s *streamBatchSender) run() {
if timerCh == nil {
select {
case <-s.stopCh:
s.failPending(s.stoppedErr())
err := s.stoppedErr()
s.waitAdmissions()
s.failBatch(batch, err)
s.failPending(err)
return
case next := <-s.reqCh:
batch = append(batch, next)
@@ -255,7 +263,10 @@ func (s *streamBatchSender) run() {
if timer != nil {
timer.Stop()
}
s.failPending(s.stoppedErr())
err := s.stoppedErr()
s.waitAdmissions()
s.failBatch(batch, err)
s.failPending(err)
return
case next := <-s.reqCh:
batch = append(batch, next)
@@ -296,10 +307,17 @@ func (s *streamBatchSender) run() {
}
s.flushMu.Unlock()
if err != nil {
s.setErr(err)
if isBatchSenderQueueWaitError(err) {
for _, item := range active {
s.finishRequest(item, err)
}
continue
}
s.markFailed(err)
for _, item := range active {
s.finishRequest(item, err)
}
s.waitAdmissions()
s.failPending(err)
return
}
@@ -312,6 +330,7 @@ func (s *streamBatchSender) run() {
func (s *streamBatchSender) nextRequest() (streamBatchRequest, bool) {
select {
case <-s.stopCh:
s.waitAdmissions()
s.failPending(s.stoppedErr())
return streamBatchRequest{}, false
case req := <-s.reqCh:
@@ -347,6 +366,9 @@ func (s *streamBatchSender) flush(requests []streamBatchRequest) error {
lockAcquired, err := s.binding.withConnWriteLockContextStopDeadlineManaged(writeCtx, s.stopCh, writeDeadlineFromTimeout(writeTimeout), func(conn net.Conn) error {
return writeFramedPayloadBatchUnlocked(conn, queue, payloads)
})
if !lockAcquired && isBatchSenderQueueWaitCause(err) {
return newBatchSenderQueueWaitError(err)
}
s.binding.observeStreamAdaptivePayloadWrite(payloadBytes, time.Since(started), writeTimeout, err)
if lockAcquired && err != nil {
// A failed framed write may have emitted only part of a frame.
@@ -522,10 +544,8 @@ func (s *streamBatchSender) stop() {
if s == nil {
return
}
s.stopOnce.Do(func() {
s.setErr(errTransportDetached)
close(s.stopCh)
})
s.markFailed(errTransportDetached)
s.waitAdmissions()
<-s.doneCh
// Direct submissions flush on the caller goroutine rather than run(). Wait
// for that path too before declaring the binding safe to hand off.
@@ -533,6 +553,28 @@ func (s *streamBatchSender) stop() {
s.flushMu.Unlock()
}
func (s *streamBatchSender) enqueue(req streamBatchRequest) bool {
if s == nil {
return false
}
s.admissionMu.Lock()
if s.admissionClosed {
s.admissionMu.Unlock()
return false
}
s.admitting.Add(1)
s.admissionMu.Unlock()
defer s.admitting.Done()
select {
case <-req.ctx.Done():
return false
case <-s.stopCh:
return false
case s.reqCh <- req:
return true
}
}
func (s *streamBatchSender) failPending(err error) {
for {
select {
@@ -544,6 +586,12 @@ func (s *streamBatchSender) failPending(err error) {
}
}
func (s *streamBatchSender) failBatch(batch []streamBatchRequest, err error) {
for _, item := range batch {
s.finishRequest(item, err)
}
}
func (s *streamBatchSender) setErr(err error) {
if s == nil || err == nil {
return
@@ -555,6 +603,25 @@ func (s *streamBatchSender) setErr(err error) {
s.errMu.Unlock()
}
func (s *streamBatchSender) markFailed(err error) {
if s == nil {
return
}
s.setErr(err)
s.stopOnce.Do(func() {
s.admissionMu.Lock()
s.admissionClosed = true
close(s.stopCh)
s.admissionMu.Unlock()
})
}
func (s *streamBatchSender) waitAdmissions() {
if s != nil {
s.admitting.Wait()
}
}
func (s *streamBatchSender) errSnapshot() error {
if s == nil {
return errTransportDetached