Files
notify/client.go
T
b612 1f2e74acca fix(notify): 修复传输生命周期竞态,完善背压与协议边界
- 完善 stream/bulk DataID 分配、预留和双向命名空间,修复并发打开及 dedicated/shared 回退时的 ID 冲突
- 将收发、回复、恢复任务和 sidecar 绑定原始会话与物理连接,防止重连后的旧消息误操作新连接
- 加强 close/reset 身份校验及实例移除检查,修复 dedicated attach 失败、通道引用和资源回收竞态
- 收紧批量发送器停止准入,确保在途入队完成后统一清理请求、缓冲区和等待者
- 修复 record 满队列死锁、取消时序号消耗及关闭竞态,确保关闭有界并返回真实错误
- 增加协商式 record 逻辑半关闭,保留反向 ACK;通过 reset 传递 RecordFailure,避免背压掩盖原始失败原因
- 补齐帧长度、批次数量、序号溢出和未确认窗口校验,提前拒绝超限数据并按字节预算拆批
- 为入站分发增加全局及单连接的条数、字节预算和阻塞背压,关闭时唤醒等待者,消除正常断连日志噪音
- 完善 bulk 窗口释放失败处理与传输诊断,补充并发、重连、背压、协议边界及真实 TCP 回归覆盖
2026-09-23 15:33:17 +08:00

170 lines
7.0 KiB
Go

package notify
import (
"b612.me/stario"
"context"
"net"
"sync"
"sync/atomic"
"time"
)
type ClientCommon struct {
alive atomic.Value
status Status
byeFromServer bool
conn net.Conn
mu sync.Mutex
msgID uint64
peerIdentity string
sessionEpoch uint64
sessionOwnerState atomic.Int32
sessionRuntime atomic.Pointer[clientSessionRuntime]
connectSource atomic.Pointer[clientConnectSource]
queue *stario.StarQueue
stopFn context.CancelFunc
stopCtx context.Context
parallelNum int
maxReadTimeout time.Duration
maxWriteTimeout time.Duration
keyExchangeFn func(c Client) error
linkMu sync.RWMutex
linkFns map[string]func(message *Message)
defaultFns func(message *Message)
msgEn func([]byte, []byte) []byte
msgDe func([]byte, []byte) []byte
fastStreamEncode transportFastStreamEncoder
fastBulkEncode transportFastBulkEncoder
fastPlainEncode transportFastPlainEncoder
modernPSKRuntime *modernPSKCodecRuntime
handshakeRsaPubKey []byte
SecretKey []byte
transportProtection atomic.Pointer[transportProtectionProfile]
inboundTransitionProfile atomic.Pointer[transportProtectionProfile]
peerAttachSecurity atomic.Pointer[peerAttachSecurityState]
securityBootstrap transportProtectionProfile
securitySteady transportProtectionProfile
securitySteadyNegotiated transportProtectionProfile
securityAuthMode AuthMode
securityProtectionMode ProtectionMode
securityRequireForwardSecrecy bool
securityConfigured bool
peerAttachAuthenticated bool
peerAttachAuthFallback bool
peerAttachAt int64
noFinSyncMsgMaxKeepSeconds int
lastHeartbeat int64
heartbeatPeriod time.Duration
wg stario.WaitGroup
netType NetType
showError bool
skipKeyExchange bool
useHeartBeat bool
sequenceDe func([]byte) (interface{}, error)
sequenceEn func(interface{}) ([]byte, error)
logicalSession *logicalSessionState
onFileEvent func(FileEvent)
fileEventObserver func(FileEvent)
fileTransferCfg fileTransferConfig
signalReliableCfg signalReliabilityConfig
streamRuntime *streamRuntime
recordRuntime *recordRuntime
bulkRuntime *bulkRuntime
bulkRecovery *bulkRecoveryQueue
bulkRecoveryMu sync.Mutex
bulkDefaultOpenMode BulkOpenMode
bulkNetworkProfile BulkNetworkProfile
bulkOpenTuning BulkOpenTuning
bulkDedicatedAttachLimit int
bulkDedicatedAttachSem chan struct{}
bulkDedicatedAttachRetry int
bulkDedicatedAttachBackoff time.Duration
bulkDedicatedDialTimeout time.Duration
bulkDedicatedHelloTimeout time.Duration
bulkDedicatedActiveLimit int
bulkDedicatedActive atomic.Int32
bulkDedicatedActiveWait chan struct{}
bulkDedicatedLaneLimit int
bulkDedicatedSidecarMu sync.Mutex
bulkDedicatedLanes map[uint32]*bulkDedicatedLane
bulkDedicatedNextLaneID uint32
bulkAttachAttemptCount atomic.Int64
bulkAttachRetryCount atomic.Int64
bulkAttachSuccessCount atomic.Int64
bulkAttachFallbackCount atomic.Int64
connectionRetryState *connectionRetryState
securityReadyCheck bool
debugMode bool
}
func NewClient() Client {
transport := defaultModernPSKTransportBundle()
var client = ClientCommon{
maxReadTimeout: 0,
maxWriteTimeout: 0,
peerIdentity: newClientPeerIdentity(),
sequenceEn: encode,
sequenceDe: Decode,
keyExchangeFn: aesRsaHello,
SecretKey: nil,
handshakeRsaPubKey: defaultRsaPubKey,
msgEn: transport.msgEn,
msgDe: transport.msgDe,
fastStreamEncode: transport.fastStreamEncode,
fastBulkEncode: transport.fastBulkEncode,
fastPlainEncode: transport.fastPlainEncode,
skipKeyExchange: true,
securityReadyCheck: true,
bulkDefaultOpenMode: BulkOpenModeShared,
bulkNetworkProfile: BulkNetworkProfileDefault,
bulkOpenTuning: defaultBulkOpenTuning(),
bulkDedicatedAttachLimit: defaultBulkDedicatedAttachLimit,
bulkDedicatedAttachRetry: defaultBulkDedicatedAttachRetry,
bulkDedicatedAttachBackoff: defaultBulkDedicatedAttachBackoff,
bulkDedicatedDialTimeout: defaultBulkDedicatedDialTimeout,
bulkDedicatedHelloTimeout: defaultBulkDedicatedHelloTimeout,
bulkDedicatedActiveLimit: defaultBulkDedicatedActiveLimit,
bulkDedicatedActiveWait: make(chan struct{}),
bulkDedicatedLaneLimit: defaultBulkDedicatedLaneLimit,
}
client.alive.Store(false)
client.useHeartBeat = true
client.heartbeatPeriod = time.Second * 20
client.linkFns = make(map[string]func(*Message))
client.defaultFns = func(message *Message) {
return
}
client.wg = stario.NewWaitGroup(0)
client.fileTransferCfg = defaultFileTransferConfig()
client.signalReliableCfg = defaultSignalReliabilityConfig()
client.logicalSession = newLogicalSessionState(client.fileTransferCfg, client.signalReliableCfg)
client.streamRuntime = newStreamRuntime("cstrm")
client.recordRuntime = newRecordRuntime()
client.bulkRuntime = newBulkRuntime("cblk")
client.bulkRecovery = newBulkRecoveryQueue(client.reportBulkRecoveryError)
client.bulkDedicatedLanes = make(map[uint32]*bulkDedicatedLane)
if client.bulkDedicatedAttachLimit > 0 {
client.bulkDedicatedAttachSem = make(chan struct{}, client.bulkDedicatedAttachLimit)
}
client.connectionRetryState = newConnectionRetryState()
client.onFileEvent = normalizeFileEventCallback(nil)
client.fileEventObserver = normalizeFileEventCallback(nil)
client.stopCtx, client.stopFn = context.WithCancel(context.Background())
client.sessionRuntime.Store(newClientSessionRuntimeBase(client.stopCtx, client.stopFn))
client.setClientTransportProtectionProfile(defaultTransportProtectionProfile())
client.peerAttachSecurity.Store(defaultPeerAttachSecurityState())
bindClientStreamControl(&client)
bindClientBulkControl(&client)
client.getTransferState().setBuiltinHandler(client.builtinFileTransferHandler)
return &client
}
func (c *ClientCommon) maxWriteTimeoutSnapshot() time.Duration {
if c == nil {
return 0
}
c.mu.Lock()
defer c.mu.Unlock()
return c.maxWriteTimeout
}