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:
+143
-27
@@ -3,6 +3,7 @@ package notify
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -28,6 +29,7 @@ type StreamOpenResponse struct {
|
||||
|
||||
type StreamCloseRequest struct {
|
||||
StreamID string
|
||||
DataID uint64
|
||||
Full bool
|
||||
}
|
||||
|
||||
@@ -38,9 +40,10 @@ type StreamCloseResponse struct {
|
||||
}
|
||||
|
||||
type StreamResetRequest struct {
|
||||
StreamID string
|
||||
DataID uint64
|
||||
Error string
|
||||
StreamID string
|
||||
DataID uint64
|
||||
Error string
|
||||
RecordFailure *RecordFailure
|
||||
}
|
||||
|
||||
type StreamResetResponse struct {
|
||||
@@ -87,6 +90,15 @@ func (c *ClientCommon) handleInboundStreamOpen(msg *Message) {
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
route := msg.clientRoute
|
||||
if !route.bound() {
|
||||
route = c.clientSessionRouteSnapshot()
|
||||
}
|
||||
if err := c.ensureClientSessionRouteSendReady(route); err != nil {
|
||||
resp.Error = err.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
runtime := c.getStreamRuntime()
|
||||
if runtime == nil {
|
||||
resp.Error = errStreamRuntimeNil.Error()
|
||||
@@ -94,17 +106,38 @@ func (c *ClientCommon) handleInboundStreamOpen(msg *Message) {
|
||||
return
|
||||
}
|
||||
scope := clientFileScope()
|
||||
if existing, ok := runtime.lookup(scope, req.StreamID); ok && !existing.acceptsClientSessionRoute(route) {
|
||||
existing.markReset(transportDetachedSessionEpochError())
|
||||
}
|
||||
req.FastPathVersion = negotiateStreamFastPathVersion(req.FastPathVersion)
|
||||
resp.FastPathVersion = req.FastPathVersion
|
||||
if req.DataID == 0 {
|
||||
req.DataID = runtime.nextDataID()
|
||||
resp.DataID = req.DataID
|
||||
}
|
||||
req.Metadata, resp.Metadata = negotiateRecordStreamOpenMetadata(req.Channel, req.Metadata)
|
||||
stream := newStreamHandle(c.clientStopContextSnapshot(), runtime, scope, req, c.currentClientSessionEpoch(), nil, nil, 0, clientStreamCloseSender(c), clientStreamResetSender(c), clientStreamDataSender(c, c.currentClientSessionEpoch()), runtime.configSnapshot())
|
||||
parent := clientSessionRouteContext(route)
|
||||
if parent == nil {
|
||||
parent = c.clientStopContextSnapshot()
|
||||
}
|
||||
stream := newStreamHandle(parent, runtime, scope, req, route.epoch, nil, nil, 0, clientStreamCloseSender(c), clientStreamResetSender(c), clientStreamDataSender(c, route), runtime.configSnapshot())
|
||||
stream.setClientSnapshotOwner(c)
|
||||
stream.setAddrSnapshot(c.clientStreamAddrSnapshot())
|
||||
if err := runtime.register(scope, stream); err != nil {
|
||||
stream.setClientSessionRoute(route)
|
||||
stream.setAddrSnapshot(c.clientStreamAddrSnapshotAtRoute(route))
|
||||
if err := runtime.adoptInbound(scope, stream); err != nil {
|
||||
resp.Error = err.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if err := c.ensureClientSessionRouteSendReady(route); err != nil {
|
||||
runtime.remove(scope, stream)
|
||||
stream.markReset(err)
|
||||
resp.Error = err.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if !stream.acceptDispatchAllowed() || !stream.claimAcceptDispatch() {
|
||||
err := stream.resetErrSnapshot()
|
||||
if err == nil {
|
||||
err = transportDetachedSessionEpochError()
|
||||
stream.markReset(err)
|
||||
}
|
||||
resp.Error = err.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
@@ -117,6 +150,7 @@ func (c *ClientCommon) handleInboundStreamOpen(msg *Message) {
|
||||
return
|
||||
}
|
||||
resp.Accepted = true
|
||||
resp.DataID = stream.dataIDSnapshot()
|
||||
resp.TransportGeneration = stream.TransportGeneration()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
@@ -129,6 +163,7 @@ func (c *ClientCommon) handleInboundStreamOpen(msg *Message) {
|
||||
return
|
||||
}
|
||||
resp.Accepted = true
|
||||
resp.DataID = stream.dataIDSnapshot()
|
||||
resp.TransportGeneration = stream.TransportGeneration()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
@@ -181,16 +216,37 @@ func (s *ServerCommon) handleInboundStreamOpen(msg *Message) {
|
||||
return
|
||||
}
|
||||
transport := messageTransportConnSnapshot(msg)
|
||||
if transport != nil && !transport.IsCurrent() {
|
||||
resp.Error = transportDetachedErrorForTransport(transport).Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
scope := serverFileScope(logical)
|
||||
if existing, ok := runtime.lookup(scope, req.StreamID); ok && !existing.acceptsTransportGeneration(transport) {
|
||||
existing.markReset(transportDetachedGenerationMismatchError(existing.TransportGeneration(), transport))
|
||||
}
|
||||
req.FastPathVersion = negotiateStreamFastPathVersion(req.FastPathVersion)
|
||||
resp.FastPathVersion = req.FastPathVersion
|
||||
if req.DataID == 0 {
|
||||
req.DataID = runtime.nextDataID()
|
||||
resp.DataID = req.DataID
|
||||
}
|
||||
req.Metadata, resp.Metadata = negotiateRecordStreamOpenMetadata(req.Channel, req.Metadata)
|
||||
stream := newStreamHandle(logical.stopContextSnapshot(), runtime, scope, req, 0, logical, transport, streamTransportGeneration(logical, transport), serverStreamCloseSender(s, logical, transport), serverStreamResetSender(s, logical, transport), serverStreamDataSender(s, transport), runtime.configSnapshot())
|
||||
if err := runtime.register(scope, stream); err != nil {
|
||||
if err := runtime.adoptInbound(scope, stream); err != nil {
|
||||
resp.Error = err.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if transport != nil && !transport.IsCurrent() {
|
||||
runtime.remove(scope, stream)
|
||||
stream.markReset(transportDetachedErrorForTransport(transport))
|
||||
resp.Error = transportDetachedErrorForTransport(transport).Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if !stream.acceptDispatchAllowed() || !stream.claimAcceptDispatch() {
|
||||
err := stream.resetErrSnapshot()
|
||||
if err == nil {
|
||||
err = transportDetachedErrorForTransport(transport)
|
||||
stream.markReset(err)
|
||||
}
|
||||
resp.Error = err.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
@@ -203,6 +259,7 @@ func (s *ServerCommon) handleInboundStreamOpen(msg *Message) {
|
||||
return
|
||||
}
|
||||
resp.Accepted = true
|
||||
resp.DataID = stream.dataIDSnapshot()
|
||||
resp.TransportGeneration = stream.TransportGeneration()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
@@ -215,6 +272,7 @@ func (s *ServerCommon) handleInboundStreamOpen(msg *Message) {
|
||||
return
|
||||
}
|
||||
resp.Accepted = true
|
||||
resp.DataID = stream.dataIDSnapshot()
|
||||
resp.TransportGeneration = stream.TransportGeneration()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
@@ -262,12 +320,21 @@ func (c *ClientCommon) handleInboundStreamClose(msg *Message) {
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
stream, ok := runtime.lookup(clientFileScope(), req.StreamID)
|
||||
route := msg.clientRoute
|
||||
if !route.bound() {
|
||||
route = c.clientSessionRouteSnapshot()
|
||||
}
|
||||
stream, ok := runtime.lookupControl(clientFileScope(), req.StreamID, req.DataID)
|
||||
if !ok {
|
||||
resp.Error = errStreamNotFound.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if !stream.acceptsClientSessionRoute(route) {
|
||||
resp.Error = transportDetachedSessionEpochError().Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if req.Full {
|
||||
stream.markPeerClosed()
|
||||
} else {
|
||||
@@ -293,12 +360,17 @@ func (s *ServerCommon) handleInboundStreamClose(msg *Message) {
|
||||
}
|
||||
logical := messageLogicalConnSnapshot(msg)
|
||||
scope := serverFileScope(logical)
|
||||
stream, ok := runtime.lookup(scope, req.StreamID)
|
||||
stream, ok := runtime.lookupControl(scope, req.StreamID, req.DataID)
|
||||
if !ok {
|
||||
resp.Error = errStreamNotFound.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if !stream.acceptsTransportGeneration(messageTransportConnSnapshot(msg)) {
|
||||
resp.Error = transportDetachedGenerationMismatchError(stream.TransportGeneration(), messageTransportConnSnapshot(msg)).Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if req.Full {
|
||||
stream.markPeerClosed()
|
||||
} else {
|
||||
@@ -322,19 +394,25 @@ func (c *ClientCommon) handleInboundStreamReset(msg *Message) {
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
stream, ok := runtime.lookup(clientFileScope(), req.StreamID)
|
||||
if !ok && req.DataID != 0 {
|
||||
stream, ok = runtime.lookupByDataID(clientFileScope(), req.DataID)
|
||||
route := msg.clientRoute
|
||||
if !route.bound() {
|
||||
route = c.clientSessionRouteSnapshot()
|
||||
}
|
||||
stream, ok := runtime.lookupControl(clientFileScope(), req.StreamID, req.DataID)
|
||||
if !ok {
|
||||
resp.Error = errStreamNotFound.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if !stream.acceptsClientSessionRoute(route) {
|
||||
resp.Error = transportDetachedSessionEpochError().Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if resp.StreamID == "" {
|
||||
resp.StreamID = stream.ID()
|
||||
}
|
||||
stream.markReset(streamResetError(streamRemoteResetError(req.Error)))
|
||||
stream.markReset(req.resetError(stream.Channel()))
|
||||
resp.Accepted = true
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
}
|
||||
@@ -355,19 +433,21 @@ func (s *ServerCommon) handleInboundStreamReset(msg *Message) {
|
||||
}
|
||||
logical := messageLogicalConnSnapshot(msg)
|
||||
scope := serverFileScope(logical)
|
||||
stream, ok := runtime.lookup(scope, req.StreamID)
|
||||
if !ok && req.DataID != 0 {
|
||||
stream, ok = runtime.lookupByDataID(scope, req.DataID)
|
||||
}
|
||||
stream, ok := runtime.lookupControl(scope, req.StreamID, req.DataID)
|
||||
if !ok {
|
||||
resp.Error = errStreamNotFound.Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if !stream.acceptsTransportGeneration(messageTransportConnSnapshot(msg)) {
|
||||
resp.Error = transportDetachedGenerationMismatchError(stream.TransportGeneration(), messageTransportConnSnapshot(msg)).Error()
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
return
|
||||
}
|
||||
if resp.StreamID == "" {
|
||||
resp.StreamID = stream.ID()
|
||||
}
|
||||
stream.markReset(streamResetError(streamRemoteResetError(req.Error)))
|
||||
stream.markReset(req.resetError(stream.Channel()))
|
||||
resp.Accepted = true
|
||||
replyStreamControlIfNeeded(msg, resp)
|
||||
}
|
||||
@@ -390,6 +470,17 @@ func sendStreamOpenClient(ctx context.Context, c Client, req StreamOpenRequest)
|
||||
return decodeStreamOpenResponse(msg)
|
||||
}
|
||||
|
||||
func sendStreamOpenClientAtRoute(ctx context.Context, c *ClientCommon, route clientSessionRoute, req StreamOpenRequest) (StreamOpenResponse, error) {
|
||||
if c == nil {
|
||||
return StreamOpenResponse{}, errStreamClientNil
|
||||
}
|
||||
msg, err := c.sendObjCtxAtRoute(ctx, route, StreamOpenSignalKey, req)
|
||||
if err != nil {
|
||||
return StreamOpenResponse{}, err
|
||||
}
|
||||
return decodeStreamOpenResponse(msg)
|
||||
}
|
||||
|
||||
func sendStreamOpenServerLogical(ctx context.Context, s Server, logical *LogicalConn, req StreamOpenRequest) (StreamOpenResponse, error) {
|
||||
if s == nil {
|
||||
return StreamOpenResponse{}, errStreamServerNil
|
||||
@@ -429,6 +520,17 @@ func sendStreamCloseClient(ctx context.Context, c Client, req StreamCloseRequest
|
||||
return decodeStreamCloseResponse(msg)
|
||||
}
|
||||
|
||||
func sendStreamCloseClientAtRoute(ctx context.Context, c *ClientCommon, route clientSessionRoute, req StreamCloseRequest) (StreamCloseResponse, error) {
|
||||
if c == nil {
|
||||
return StreamCloseResponse{}, errStreamClientNil
|
||||
}
|
||||
msg, err := c.sendObjCtxAtRoute(ctx, route, StreamCloseSignalKey, req)
|
||||
if err != nil {
|
||||
return StreamCloseResponse{}, err
|
||||
}
|
||||
return decodeStreamCloseResponse(msg)
|
||||
}
|
||||
|
||||
func sendStreamCloseServerLogical(ctx context.Context, s Server, logical *LogicalConn, req StreamCloseRequest) (StreamCloseResponse, error) {
|
||||
if s == nil {
|
||||
return StreamCloseResponse{}, errStreamServerNil
|
||||
@@ -468,6 +570,17 @@ func sendStreamResetClient(ctx context.Context, c Client, req StreamResetRequest
|
||||
return decodeStreamResetResponse(msg)
|
||||
}
|
||||
|
||||
func sendStreamResetClientAtRoute(ctx context.Context, c *ClientCommon, route clientSessionRoute, req StreamResetRequest) (StreamResetResponse, error) {
|
||||
if c == nil {
|
||||
return StreamResetResponse{}, errStreamClientNil
|
||||
}
|
||||
msg, err := c.sendObjCtxAtRoute(ctx, route, StreamResetSignalKey, req)
|
||||
if err != nil {
|
||||
return StreamResetResponse{}, err
|
||||
}
|
||||
return decodeStreamResetResponse(msg)
|
||||
}
|
||||
|
||||
func sendStreamResetServerLogical(ctx context.Context, s Server, logical *LogicalConn, req StreamResetRequest) (StreamResetResponse, error) {
|
||||
if s == nil {
|
||||
return StreamResetResponse{}, errStreamServerNil
|
||||
@@ -533,7 +646,7 @@ func decodeStreamResetRequest(msg *Message) (StreamResetRequest, error) {
|
||||
if err := msg.Value.Orm(&req); err != nil {
|
||||
return StreamResetRequest{}, err
|
||||
}
|
||||
if req.StreamID == "" {
|
||||
if req.StreamID == "" && req.DataID == 0 {
|
||||
return StreamResetRequest{}, errStreamIDEmpty
|
||||
}
|
||||
return req, nil
|
||||
@@ -580,6 +693,9 @@ func streamControlResultError(op string, accepted bool, message string, callErr
|
||||
}
|
||||
|
||||
func streamControlMessageError(message string) error {
|
||||
if message == errTransportDetached.Error() || strings.HasPrefix(message, errTransportDetached.Error()+":") {
|
||||
return errTransportDetached
|
||||
}
|
||||
switch message {
|
||||
case errStreamNotFound.Error():
|
||||
return errStreamNotFound
|
||||
|
||||
Reference in New Issue
Block a user