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