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
+274 -15
View File
@@ -9,24 +9,38 @@ import (
)
type streamRuntime struct {
rolePrefix string
seq atomic.Uint64
dataSeq atomic.Uint64
rolePrefix string
seq atomic.Uint64
dataSeq uint64
peerDataSeq uint64
dataStart uint64
dataStep uint64
mu sync.RWMutex
handler func(StreamAcceptInfo) error
streams map[string]*streamHandle
data map[string]map[uint64]*streamHandle
cfg streamConfig
flow *streamFlowController
mu sync.RWMutex
handler func(StreamAcceptInfo) error
streams map[string]*streamHandle
data map[string]map[uint64]*streamHandle
reserved map[string]map[uint64]struct{}
cfg streamConfig
flow *streamFlowController
}
func newStreamRuntime(rolePrefix string) *streamRuntime {
cfg := defaultStreamConfig()
dataStart, dataStep := uint64(1), uint64(1)
if rolePrefix == "cstrm" {
dataStep = 2
} else if rolePrefix == "sstrm" {
dataStart = 2
dataStep = 2
}
return &streamRuntime{
rolePrefix: rolePrefix,
dataStart: dataStart,
dataStep: dataStep,
streams: make(map[string]*streamHandle),
data: make(map[string]map[uint64]*streamHandle),
reserved: make(map[string]map[uint64]struct{}),
cfg: cfg,
flow: newStreamFlowController(cfg),
}
@@ -43,7 +57,123 @@ func (r *streamRuntime) nextDataID() uint64 {
if r == nil {
return 0
}
return r.dataSeq.Add(1)
r.mu.Lock()
defer r.mu.Unlock()
id, _ := r.nextDataIDLocked(defaultFileScope, false)
return id
}
func (r *streamRuntime) reserveDataID(scope string) (uint64, error) {
if r == nil {
return 0, errStreamRuntimeNil
}
scope = normalizeFileScope(scope)
r.mu.Lock()
defer r.mu.Unlock()
id, err := r.nextDataIDLocked(scope, true)
return id, err
}
func (r *streamRuntime) releaseDataID(scope string, dataID uint64) {
if r == nil || dataID == 0 {
return
}
scope = normalizeFileScope(scope)
r.mu.Lock()
defer r.mu.Unlock()
if reserved := r.reserved[scope]; reserved != nil {
delete(reserved, dataID)
if len(reserved) == 0 {
delete(r.reserved, scope)
}
}
}
func (r *streamRuntime) nextDataIDLocked(scope string, reserve bool) (uint64, error) {
if r == nil {
return 0, errStreamRuntimeNil
}
for {
candidate, ok := r.nextDataCandidateLocked(false)
if !ok {
return 0, errStreamDataIDExhausted
}
if r.dataInUseLocked(scope, candidate) {
continue
}
if reserve {
reserved := r.reserved[scope]
if reserved == nil {
reserved = make(map[uint64]struct{})
r.reserved[scope] = reserved
}
reserved[candidate] = struct{}{}
}
return candidate, nil
}
}
func (r *streamRuntime) nextPeerDataIDLocked(scope string) (uint64, error) {
if r == nil {
return 0, errStreamRuntimeNil
}
for {
candidate, ok := r.nextDataCandidateLocked(true)
if !ok {
return 0, errStreamDataIDExhausted
}
if r.dataInUseLocked(scope, candidate) {
continue
}
return candidate, nil
}
}
func (r *streamRuntime) nextDataCandidateLocked(peer bool) (uint64, bool) {
seq := &r.dataSeq
start, step := r.dataStart, r.dataStep
if peer && step == 2 {
seq = &r.peerDataSeq
start = 3 - r.dataStart
}
if step == 0 {
step = 1
}
if *seq == 0 {
if start == 0 {
return 0, false
}
*seq = start
return start, true
}
if *seq > ^uint64(0)-step {
return 0, false
}
candidate := *seq + step
if step == 2 && candidate%2 != start%2 {
if candidate == ^uint64(0) {
return 0, false
}
candidate++
}
*seq = candidate
return candidate, true
}
func (r *streamRuntime) dataInUseLocked(scope string, dataID uint64) bool {
if dataID == 0 {
return true
}
if dataScope := r.data[scope]; dataScope != nil {
if _, ok := dataScope[dataID]; ok {
return true
}
}
if reserved := r.reserved[scope]; reserved != nil {
_, ok := reserved[dataID]
return ok
}
return false
}
func (r *streamRuntime) setHandler(fn func(StreamAcceptInfo) error) {
@@ -65,6 +195,18 @@ func (r *streamRuntime) handlerSnapshot() func(StreamAcceptInfo) error {
}
func (r *streamRuntime) register(scope string, stream *streamHandle) error {
return r.registerWithDirection(scope, stream, false, false)
}
func (r *streamRuntime) registerInbound(scope string, stream *streamHandle) error {
return r.registerWithDirection(scope, stream, true, false)
}
func (r *streamRuntime) registerReserved(scope string, stream *streamHandle) error {
return r.registerWithDirection(scope, stream, false, true)
}
func (r *streamRuntime) registerWithDirection(scope string, stream *streamHandle, inbound bool, consumeReservation bool) error {
if r == nil {
return errStreamRuntimeNil
}
@@ -78,6 +220,17 @@ func (r *streamRuntime) register(scope string, stream *streamHandle) error {
if _, ok := r.streams[key]; ok {
return errStreamAlreadyExists
}
if stream.dataID == 0 {
var err error
if inbound {
stream.dataID, err = r.nextPeerDataIDLocked(scope)
} else {
stream.dataID, err = r.nextDataIDLocked(scope, false)
}
if err != nil {
return err
}
}
if stream.dataID != 0 {
dataScope := r.data[scope]
if dataScope == nil {
@@ -87,12 +240,50 @@ func (r *streamRuntime) register(scope string, stream *streamHandle) error {
if _, ok := dataScope[stream.dataID]; ok {
return errStreamAlreadyExists
}
if reserved := r.reserved[scope]; reserved != nil {
if _, ok := reserved[stream.dataID]; ok {
if !consumeReservation {
return errStreamAlreadyExists
}
delete(reserved, stream.dataID)
if len(reserved) == 0 {
delete(r.reserved, scope)
}
}
}
dataScope[stream.dataID] = stream
}
r.streams[key] = stream
return nil
}
// adopt transfers ownership of a newly-created stream to the runtime. A
// failed registration is terminal so its child context cannot remain attached
// to the session after the caller drops the handle.
func (r *streamRuntime) adopt(scope string, stream *streamHandle) error {
err := r.register(scope, stream)
if err != nil && stream != nil {
stream.markReset(err)
}
return err
}
func (r *streamRuntime) adoptInbound(scope string, stream *streamHandle) error {
err := r.registerInbound(scope, stream)
if err != nil && stream != nil {
stream.markReset(err)
}
return err
}
func (r *streamRuntime) adoptReserved(scope string, stream *streamHandle) error {
err := r.registerReserved(scope, stream)
if err != nil && stream != nil {
stream.markReset(err)
}
return err
}
func (r *streamRuntime) lookup(scope string, streamID string) (*streamHandle, bool) {
if r == nil || streamID == "" {
return nil, false
@@ -119,17 +310,47 @@ func (r *streamRuntime) lookupByDataID(scope string, dataID uint64) (*streamHand
return stream, ok
}
func (r *streamRuntime) remove(scope string, streamID string) {
if r == nil || streamID == "" {
func (r *streamRuntime) lookupControl(scope string, streamID string, dataID uint64) (*streamHandle, bool) {
if r == nil {
return nil, false
}
scope = normalizeFileScope(scope)
r.mu.RLock()
defer r.mu.RUnlock()
if streamID != "" {
stream, ok := r.streams[streamRuntimeKey(scope, streamID)]
if !ok || stream == nil {
return nil, false
}
if dataID != 0 && stream.dataID != dataID {
return nil, false
}
return stream, true
}
if dataID == 0 {
return nil, false
}
stream := r.data[scope][dataID]
return stream, stream != nil
}
func (r *streamRuntime) remove(scope string, expected *streamHandle) {
if r == nil || expected == nil || expected.id == "" {
return
}
scope = normalizeFileScope(scope)
key := streamRuntimeKey(scope, streamID)
key := streamRuntimeKey(scope, expected.id)
r.mu.Lock()
defer r.mu.Unlock()
if stream := r.streams[key]; stream != nil && stream.dataID != 0 {
stream := r.streams[key]
if stream != expected {
return
}
if stream.dataID != 0 {
if dataScope := r.data[scope]; dataScope != nil {
delete(dataScope, stream.dataID)
if dataScope[stream.dataID] == stream {
delete(dataScope, stream.dataID)
}
if len(dataScope) == 0 {
delete(r.data, scope)
}
@@ -187,6 +408,44 @@ func (r *streamRuntime) closeScope(scope string, err error) {
}, err)
}
func (r *streamRuntime) closeClientRoute(route clientSessionRoute, err error) {
if r == nil {
return
}
if !r.mu.TryRLock() {
go r.closeClientRouteBlocking(route, err)
return
}
streams := r.collectClientRouteLocked(route)
r.mu.RUnlock()
r.resetClientRouteHandles(streams, err)
}
func (r *streamRuntime) closeClientRouteBlocking(route clientSessionRoute, err error) {
r.mu.RLock()
streams := r.collectClientRouteLocked(route)
r.mu.RUnlock()
r.resetClientRouteHandles(streams, err)
}
func (r *streamRuntime) collectClientRouteLocked(route clientSessionRoute) []*streamHandle {
streams := make([]*streamHandle, 0)
for _, stream := range r.streams {
if stream == nil || !sameClientSessionRoute(stream.clientRoute, route) {
continue
}
streams = append(streams, stream)
}
return streams
}
func (r *streamRuntime) resetClientRouteHandles(streams []*streamHandle, err error) {
resetErr := streamRuntimeCloseError(err)
for _, stream := range streams {
stream.markReset(resetErr)
}
}
func (r *streamRuntime) closeMatching(match func(string) bool, err error) {
if r == nil || match == nil {
return