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
+137 -26
View File
@@ -80,6 +80,7 @@ var (
errStreamNotFound = errors.New("stream not found")
errStreamHandlerNotConfigured = errors.New("stream handler is not configured")
errStreamDataPathNotReady = errors.New("stream data path is not implemented yet")
errStreamDataIDExhausted = errors.New("stream data id exhausted")
errStreamRejected = errors.New("stream open rejected")
errStreamReset = errors.New("stream reset")
errStreamBackpressureExceeded = errors.New("stream inbound backpressure exceeded")
@@ -153,6 +154,7 @@ type streamHandle struct {
channel StreamChannel
metadata StreamMetadata
sessionEpoch uint64
clientRoute clientSessionRoute
client *ClientCommon
logical *LogicalConn
transport *TransportConn
@@ -173,6 +175,9 @@ type streamHandle struct {
writeMu sync.Mutex
mu sync.Mutex
negotiationMu sync.RWMutex
finalizeOnce sync.Once
acceptState atomic.Uint32 // 0=pending, 1=dispatched/handled, 2=reset before dispatch
localClosed bool
localReadClosed bool
remoteClosed bool
@@ -259,14 +264,43 @@ func (s *streamHandle) acceptsClientSessionEpoch(epoch uint64) bool {
return s.sessionEpoch == epoch
}
func (s *streamHandle) setClientSessionRoute(route clientSessionRoute) {
if s == nil {
return
}
s.sessionEpoch = route.epoch
s.clientRoute = route
}
func (s *streamHandle) clientSessionRouteSnapshot() clientSessionRoute {
if s == nil {
return clientSessionRoute{}
}
if !s.clientRoute.bound() && s.client != nil {
return s.client.clientSessionRouteSnapshot()
}
return s.clientRoute
}
func (s *streamHandle) acceptsClientSessionRoute(route clientSessionRoute) bool {
if !s.acceptsClientSessionEpoch(route.epoch) {
return false
}
if s == nil || s.clientRoute.binding == nil || route.binding == nil {
return true
}
return s.clientRoute.binding == route.binding
}
func (s *streamHandle) acceptsTransportGeneration(transport *TransportConn) bool {
if s == nil {
return false
}
if s.transportGeneration == 0 || transport == nil {
generation := s.TransportGeneration()
if generation == 0 || transport == nil {
return true
}
return s.transportGeneration == transport.TransportGeneration()
return generation == transport.TransportGeneration()
}
func (s *streamHandle) ID() string {
@@ -302,9 +336,20 @@ func (s *streamHandle) fastPathVersionSnapshot() uint8 {
if s == nil {
return streamFastPathVersionV1
}
s.negotiationMu.RLock()
defer s.negotiationMu.RUnlock()
return normalizeStreamFastPathVersion(s.fastPathVersion)
}
func (s *streamHandle) setFastPathVersion(version uint8) {
if s == nil {
return
}
s.negotiationMu.Lock()
s.fastPathVersion = normalizeStreamFastPathVersion(version)
s.negotiationMu.Unlock()
}
func (s *streamHandle) Channel() StreamChannel {
if s == nil {
return StreamDataChannel
@@ -344,9 +389,57 @@ func (s *streamHandle) TransportGeneration() uint64 {
if s == nil {
return 0
}
s.negotiationMu.RLock()
defer s.negotiationMu.RUnlock()
return s.transportGeneration
}
func (s *streamHandle) setTransportGeneration(generation uint64) {
if s == nil || generation == 0 {
return
}
s.negotiationMu.Lock()
s.transportGeneration = generation
s.negotiationMu.Unlock()
}
func (s *streamHandle) acceptsCurrentTransport() bool {
if s == nil {
return false
}
if s.transport != nil {
return s.transport.IsCurrent()
}
if s.client != nil {
return s.client.clientSessionRouteCurrent(s.clientSessionRouteSnapshot())
}
return true
}
func (s *streamHandle) acceptDispatchAllowed() bool {
if s == nil || s.acceptState.Load() == 2 {
return false
}
if err := s.resetErrSnapshot(); err != nil {
return false
}
return s.acceptsCurrentTransport()
}
func (s *streamHandle) claimAcceptDispatch() bool {
if s == nil {
return false
}
return s.acceptState.CompareAndSwap(0, 1)
}
func (s *streamHandle) markAcceptHandled() {
if s == nil {
return
}
s.acceptState.CompareAndSwap(0, 1)
}
func (s *streamHandle) LocalAddr() net.Addr {
if s == nil {
return nil
@@ -780,20 +873,28 @@ func (s *streamHandle) Reset(err error) error {
return err
}
resetFn := s.resetFn
deadline := s.effectiveWriteDeadlineLocked(time.Now(), s.writeTimeout)
s.mu.Unlock()
if resetFn != nil {
ctx, cancel, err := s.newControlContext()
if err != nil {
return err
}
defer cancel()
if sendErr := resetFn(ctx, s, streamResetMessage(resetErr)); sendErr != nil {
return sendErr
}
if !s.applyResetState(resetErr) {
return s.resetErrSnapshot()
}
if resetFn == nil {
return nil
}
limit := time.Now().Add(5 * time.Second)
if deadline.IsZero() || deadline.After(limit) {
deadline = limit
}
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
done := make(chan error, 1)
go func() { done <- resetFn(ctx, s, streamResetMessage(resetErr)) }()
select {
case err := <-done:
return err
case <-ctx.Done():
return ctx.Err()
}
s.markReset(resetErr)
return nil
}
func (s *streamHandle) markRemoteClosed() {
@@ -826,17 +927,25 @@ func (s *streamHandle) markPeerClosed() {
}
func (s *streamHandle) markReset(err error) {
_ = s.applyResetState(err)
}
func (s *streamHandle) applyResetState(err error) bool {
if s == nil {
return
return false
}
s.acceptState.CompareAndSwap(0, 2)
s.mu.Lock()
if s.resetErr == nil {
s.resetErr = streamResetError(err)
s.clearBufferedDataLocked()
if s.resetErr != nil {
s.mu.Unlock()
return false
}
s.resetErr = streamResetError(err)
s.clearBufferedDataLocked()
s.mu.Unlock()
s.notifyReadable()
s.finalize()
return true
}
func (s *streamHandle) resetErrSnapshot() error {
@@ -1019,7 +1128,7 @@ func (s *streamHandle) snapshot() StreamSnapshot {
Channel: s.channel,
Metadata: cloneStreamMetadata(s.metadata),
SessionEpoch: s.sessionEpoch,
TransportGeneration: s.transportGeneration,
TransportGeneration: s.TransportGeneration(),
LocalClosed: s.localClosed,
LocalReadClosed: s.localReadClosed,
RemoteClosed: s.remoteClosed,
@@ -1059,7 +1168,7 @@ func (s *streamHandle) snapshot() StreamSnapshot {
var diag snapshotBindingDiagnostics
switch {
case s.logical != nil || s.transport != nil:
diag = snapshotBindingDiagnosticsFromLogical(s.logical, s.transport, s.transportGeneration)
diag = snapshotBindingDiagnosticsFromLogical(s.logical, s.transport, s.TransportGeneration())
case s.client != nil:
diag = snapshotBindingDiagnosticsFromClient(s.client, s.sessionEpoch)
}
@@ -1095,12 +1204,14 @@ func (s *streamHandle) finalize() {
if s == nil {
return
}
if s.cancel != nil {
s.cancel()
}
if s.runtime != nil {
s.runtime.remove(s.runtimeScope, s.id)
}
s.finalizeOnce.Do(func() {
if s.cancel != nil {
s.cancel()
}
if s.runtime != nil {
s.runtime.remove(s.runtimeScope, s)
}
})
}
func (s *streamHandle) waitReadable(ctx context.Context, notify <-chan struct{}, deadlineNotify <-chan struct{}, deadline time.Time) error {