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:
+52
-19
@@ -12,6 +12,7 @@ const (
|
||||
recordFrameTypeBatch uint8 = 1
|
||||
recordFrameTypeAck uint8 = 2
|
||||
recordFrameTypeError uint8 = 3
|
||||
recordFrameTypeFIN uint8 = 4
|
||||
recordFrameHeaderSize = 8
|
||||
recordBatchHeaderV1Size = 10
|
||||
recordBatchHeaderV2Size = 18
|
||||
@@ -33,6 +34,7 @@ type recordFrame struct {
|
||||
Type uint8
|
||||
Batch []recordOutboundMessage
|
||||
AckSeq uint64
|
||||
FinalSeq uint64
|
||||
Failure RecordFailure
|
||||
Retryable bool
|
||||
}
|
||||
@@ -41,8 +43,11 @@ func encodeRecordBatchFrame(batch []recordOutboundMessage, ackSeq uint64, useV2
|
||||
if len(batch) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(batch) > recordMaxBatchRecords {
|
||||
return nil, errRecordFrameInvalid
|
||||
}
|
||||
firstSeq := batch[0].Seq
|
||||
if firstSeq == 0 {
|
||||
if firstSeq == 0 || uint64(len(batch)-1) > ^uint64(0)-firstSeq {
|
||||
return nil, errRecordSeqInvalid
|
||||
}
|
||||
version := uint8(recordFrameVersionV1)
|
||||
@@ -57,6 +62,9 @@ func encodeRecordBatchFrame(batch []recordOutboundMessage, ackSeq uint64, useV2
|
||||
if item.Seq != wantSeq {
|
||||
return nil, errRecordSeqInvalid
|
||||
}
|
||||
if len(item.Payload) > recordMaxPayloadBytes || size > transferFrameMaxPayloadBytes-4-len(item.Payload) {
|
||||
return nil, errRecordFrameInvalid
|
||||
}
|
||||
size += 4 + len(item.Payload)
|
||||
}
|
||||
frame := make([]byte, size)
|
||||
@@ -87,13 +95,21 @@ func encodeRecordAckFrame(ackSeq uint64) ([]byte, error) {
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func encodeRecordFINFrame(finalSeq uint64) []byte {
|
||||
frame, _ := encodeRecordAckFrame(finalSeq)
|
||||
frame[5] = recordFrameTypeFIN
|
||||
return frame
|
||||
}
|
||||
|
||||
func encodeRecordErrorFrame(failure RecordFailure) ([]byte, error) {
|
||||
if failure.FailedSeq == 0 {
|
||||
return nil, errRecordSeqInvalid
|
||||
}
|
||||
codeBytes := []byte(failure.Code)
|
||||
msgBytes := []byte(failure.Message)
|
||||
frame := make([]byte, recordFrameHeaderSize+recordErrorHeaderSize+len(codeBytes)+len(msgBytes))
|
||||
const headerSize = recordFrameHeaderSize + recordErrorHeaderSize
|
||||
if len(failure.Code) > int(^uint16(0)) || len(failure.Message) > transferFrameMaxPayloadBytes-headerSize-len(failure.Code) {
|
||||
return nil, errRecordFrameInvalid
|
||||
}
|
||||
frame := make([]byte, headerSize+len(failure.Code)+len(failure.Message))
|
||||
copy(frame[:4], recordFrameMagic)
|
||||
frame[4] = recordFrameVersionV1
|
||||
frame[5] = recordFrameTypeError
|
||||
@@ -101,12 +117,12 @@ func encodeRecordErrorFrame(failure RecordFailure) ([]byte, error) {
|
||||
frame[6] = 1
|
||||
}
|
||||
binary.BigEndian.PutUint64(frame[8:16], failure.FailedSeq)
|
||||
binary.BigEndian.PutUint16(frame[16:18], uint16(len(codeBytes)))
|
||||
binary.BigEndian.PutUint32(frame[18:22], uint32(len(msgBytes)))
|
||||
binary.BigEndian.PutUint16(frame[16:18], uint16(len(failure.Code)))
|
||||
binary.BigEndian.PutUint32(frame[18:22], uint32(len(failure.Message)))
|
||||
offset := recordFrameHeaderSize + recordErrorHeaderSize
|
||||
copy(frame[offset:offset+len(codeBytes)], codeBytes)
|
||||
offset += len(codeBytes)
|
||||
copy(frame[offset:offset+len(msgBytes)], msgBytes)
|
||||
copy(frame[offset:], failure.Code)
|
||||
offset += len(failure.Code)
|
||||
copy(frame[offset:], failure.Message)
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
@@ -116,6 +132,12 @@ func decodeRecordFrame(payload []byte) (recordFrame, error) {
|
||||
}
|
||||
version := payload[4]
|
||||
frameType := payload[5]
|
||||
if frameType == recordFrameTypeFIN && version == recordFrameVersionV1 {
|
||||
if len(payload) != recordFrameHeaderSize+8 {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
return recordFrame{Version: version, Type: frameType, FinalSeq: binary.BigEndian.Uint64(payload[8:16])}, nil
|
||||
}
|
||||
switch version {
|
||||
case recordFrameVersionV1:
|
||||
switch frameType {
|
||||
@@ -174,20 +196,24 @@ func decodeRecordBatchFrameV1(payload []byte) (recordFrame, error) {
|
||||
}
|
||||
count := int(binary.BigEndian.Uint16(payload[8:10]))
|
||||
firstSeq := binary.BigEndian.Uint64(payload[10:18])
|
||||
if count <= 0 || firstSeq == 0 {
|
||||
if count <= 0 || firstSeq == 0 || uint64(count-1) > ^uint64(0)-firstSeq {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
offset := recordFrameHeaderSize + recordBatchHeaderV1Size
|
||||
if count > (len(payload)-offset)/4 {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
batch := make([]recordOutboundMessage, 0, count)
|
||||
for index := 0; index < count; index++ {
|
||||
if offset+4 > len(payload) {
|
||||
if len(payload)-offset < 4 {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
itemLen := int(binary.BigEndian.Uint32(payload[offset : offset+4]))
|
||||
wireItemLen := binary.BigEndian.Uint32(payload[offset : offset+4])
|
||||
offset += 4
|
||||
if itemLen < 0 || offset+itemLen > len(payload) {
|
||||
if uint64(wireItemLen) > uint64(len(payload)-offset) {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
itemLen := int(wireItemLen)
|
||||
item := recordOutboundMessage{
|
||||
Seq: firstSeq + uint64(index),
|
||||
Payload: append([]byte(nil), payload[offset:offset+itemLen]...),
|
||||
@@ -212,20 +238,24 @@ func decodeRecordBatchFrameV2(payload []byte) (recordFrame, error) {
|
||||
count := int(binary.BigEndian.Uint16(payload[8:10]))
|
||||
firstSeq := binary.BigEndian.Uint64(payload[10:18])
|
||||
ackSeq := binary.BigEndian.Uint64(payload[18:26])
|
||||
if count <= 0 || firstSeq == 0 {
|
||||
if count <= 0 || firstSeq == 0 || uint64(count-1) > ^uint64(0)-firstSeq {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
offset := recordFrameHeaderSize + recordBatchHeaderV2Size
|
||||
if count > (len(payload)-offset)/4 {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
batch := make([]recordOutboundMessage, 0, count)
|
||||
for index := 0; index < count; index++ {
|
||||
if offset+4 > len(payload) {
|
||||
if len(payload)-offset < 4 {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
itemLen := int(binary.BigEndian.Uint32(payload[offset : offset+4]))
|
||||
wireItemLen := binary.BigEndian.Uint32(payload[offset : offset+4])
|
||||
offset += 4
|
||||
if itemLen < 0 || offset+itemLen > len(payload) {
|
||||
if uint64(wireItemLen) > uint64(len(payload)-offset) {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
itemLen := int(wireItemLen)
|
||||
item := recordOutboundMessage{
|
||||
Seq: firstSeq + uint64(index),
|
||||
Payload: append([]byte(nil), payload[offset:offset+itemLen]...),
|
||||
@@ -250,9 +280,12 @@ func decodeRecordErrorFrame(payload []byte) (recordFrame, error) {
|
||||
}
|
||||
failedSeq := binary.BigEndian.Uint64(payload[8:16])
|
||||
codeLen := int(binary.BigEndian.Uint16(payload[16:18]))
|
||||
msgLen := int(binary.BigEndian.Uint32(payload[18:22]))
|
||||
wireMsgLen := binary.BigEndian.Uint32(payload[18:22])
|
||||
offset := recordFrameHeaderSize + recordErrorHeaderSize
|
||||
if failedSeq == 0 || offset+codeLen+msgLen != len(payload) {
|
||||
if failedSeq == 0 || len(payload)-offset < codeLen {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
if uint64(len(payload)-offset-codeLen) != uint64(wireMsgLen) {
|
||||
return recordFrame{}, errRecordFrameInvalid
|
||||
}
|
||||
failure := RecordFailure{
|
||||
|
||||
Reference in New Issue
Block a user