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