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:
+196
-51
@@ -75,12 +75,15 @@ type bulkDedicatedSender struct {
|
||||
encodeBatch func([]bulkDedicatedSendRequest) ([]byte, error)
|
||||
fail func(error)
|
||||
|
||||
reqCh chan bulkDedicatedBatchRequest
|
||||
stopCh chan struct{}
|
||||
doneCh chan struct{}
|
||||
stopOnce sync.Once
|
||||
flushMu sync.Mutex
|
||||
queued atomic.Int64
|
||||
reqCh chan bulkDedicatedBatchRequest
|
||||
stopCh chan struct{}
|
||||
doneCh chan struct{}
|
||||
stopOnce sync.Once
|
||||
admissionMu sync.Mutex
|
||||
admitting sync.WaitGroup
|
||||
admissionClosed bool
|
||||
flushMu sync.Mutex
|
||||
queued atomic.Int64
|
||||
|
||||
errMu sync.Mutex
|
||||
err error
|
||||
@@ -220,19 +223,17 @@ func (s *bulkDedicatedSender) submitBatch(ctx context.Context, items []bulkDedic
|
||||
req.Ack = make(chan error, 1)
|
||||
}
|
||||
s.queued.Add(1)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !s.enqueue(req) {
|
||||
s.queued.Add(-1)
|
||||
return normalizeStreamDeadlineError(ctx.Err())
|
||||
case <-s.stopCh:
|
||||
s.queued.Add(-1)
|
||||
return s.stoppedErr()
|
||||
case s.reqCh <- req:
|
||||
if !wait {
|
||||
return nil
|
||||
if err := ctx.Err(); err != nil {
|
||||
return normalizeStreamDeadlineError(err)
|
||||
}
|
||||
return s.waitAck(req)
|
||||
return s.stoppedErr()
|
||||
}
|
||||
if !wait {
|
||||
return nil
|
||||
}
|
||||
return s.waitAck(req)
|
||||
}
|
||||
|
||||
func (s *bulkDedicatedSender) tryDirectSubmitBatch(ctx context.Context, items []bulkDedicatedSendRequest) (bool, error) {
|
||||
@@ -276,9 +277,15 @@ func (s *bulkDedicatedSender) tryDirectSubmitBatch(ctx context.Context, items []
|
||||
default:
|
||||
}
|
||||
deadline, _ := ctx.Deadline()
|
||||
select {
|
||||
case <-s.stopCh:
|
||||
return true, s.stoppedErr()
|
||||
default:
|
||||
}
|
||||
if err := s.flush(items, deadline); err != nil {
|
||||
err = normalizeDedicatedBulkSendError(err)
|
||||
s.setErr(err)
|
||||
s.markFailed(err)
|
||||
s.waitAdmissions()
|
||||
s.failPending(err)
|
||||
if s.fail != nil {
|
||||
go s.fail(err)
|
||||
@@ -311,11 +318,36 @@ func (s *bulkDedicatedSender) stop() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.stopOnce.Do(func() {
|
||||
s.setErr(errTransportDetached)
|
||||
close(s.stopCh)
|
||||
})
|
||||
s.markFailed(errTransportDetached)
|
||||
s.waitAdmissions()
|
||||
<-s.doneCh
|
||||
// Direct submissions flush on the caller goroutine rather than run().
|
||||
// Wait for that path before allowing the underlying connection to close or
|
||||
// be handed to a replacement sender.
|
||||
s.flushMu.Lock()
|
||||
s.flushMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *bulkDedicatedSender) enqueue(req bulkDedicatedBatchRequest) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
s.admissionMu.Lock()
|
||||
if s.admissionClosed {
|
||||
s.admissionMu.Unlock()
|
||||
return false
|
||||
}
|
||||
s.admitting.Add(1)
|
||||
s.admissionMu.Unlock()
|
||||
defer s.admitting.Done()
|
||||
select {
|
||||
case <-req.Ctx.Done():
|
||||
return false
|
||||
case <-s.stopCh:
|
||||
return false
|
||||
case s.reqCh <- req:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *bulkDedicatedSender) run() {
|
||||
@@ -337,13 +369,19 @@ func (s *bulkDedicatedSender) run() {
|
||||
s.flushMu.Lock()
|
||||
err := s.errSnapshot()
|
||||
if err == nil {
|
||||
err = s.flush(req.Items, req.Deadline)
|
||||
select {
|
||||
case <-s.stopCh:
|
||||
err = s.stoppedErr()
|
||||
default:
|
||||
err = s.flush(req.Items, req.Deadline)
|
||||
}
|
||||
}
|
||||
s.flushMu.Unlock()
|
||||
if err != nil {
|
||||
err = normalizeDedicatedBulkSendError(err)
|
||||
s.setErr(err)
|
||||
s.markFailed(err)
|
||||
s.finishRequest(req, err)
|
||||
s.waitAdmissions()
|
||||
s.failPending(err)
|
||||
if s.fail != nil {
|
||||
go s.fail(err)
|
||||
@@ -390,6 +428,7 @@ func (r bulkDedicatedBatchRequest) canceledErr() error {
|
||||
func (s *bulkDedicatedSender) nextRequest() (bulkDedicatedBatchRequest, bool) {
|
||||
select {
|
||||
case <-s.stopCh:
|
||||
s.waitAdmissions()
|
||||
s.failPending(s.stoppedErr())
|
||||
return bulkDedicatedBatchRequest{}, false
|
||||
case req := <-s.reqCh:
|
||||
@@ -455,6 +494,25 @@ func (s *bulkDedicatedSender) setErr(err error) {
|
||||
s.errMu.Unlock()
|
||||
}
|
||||
|
||||
func (s *bulkDedicatedSender) markFailed(err error) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.setErr(err)
|
||||
s.stopOnce.Do(func() {
|
||||
s.admissionMu.Lock()
|
||||
s.admissionClosed = true
|
||||
close(s.stopCh)
|
||||
s.admissionMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
func (s *bulkDedicatedSender) waitAdmissions() {
|
||||
if s != nil {
|
||||
s.admitting.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *bulkDedicatedSender) errSnapshot() error {
|
||||
if s == nil {
|
||||
return errTransportDetached
|
||||
@@ -525,11 +583,77 @@ func bulkDedicatedBatchesPlainLen(batches []bulkDedicatedOutboundBatch) int {
|
||||
}
|
||||
}
|
||||
|
||||
func encodeBulkDedicatedBatchesPlain(batches []bulkDedicatedOutboundBatch) ([]byte, error) {
|
||||
if len(batches) == 0 {
|
||||
return nil, errBulkFastPayloadInvalid
|
||||
func bulkDedicatedBatchesPlainLenChecked(batches []bulkDedicatedOutboundBatch) (int, error) {
|
||||
switch len(batches) {
|
||||
case 0:
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
case 1:
|
||||
return bulkDedicatedBatchPlainLenChecked(batches[0].Items)
|
||||
}
|
||||
if len(batches) > bulkDedicatedBatchMaxItems {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
total := bulkDedicatedSuperBatchHeaderLen
|
||||
totalItems := 0
|
||||
for _, batch := range batches {
|
||||
if batch.DataID == 0 || len(batch.Items) == 0 || len(batch.Items) > bulkDedicatedBatchMaxItems {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
totalItems += len(batch.Items)
|
||||
if totalItems > bulkDedicatedBatchMaxItems {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
if total > bulkDedicatedBatchMaxPlainBytes-bulkDedicatedSuperBatchGroupHeaderLen {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
total += bulkDedicatedSuperBatchGroupHeaderLen
|
||||
itemsLen, err := bulkDedicatedSendRequestsLenChecked(batch.Items)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if total > bulkDedicatedBatchMaxPlainBytes-itemsLen {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
total += itemsLen
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func bulkDedicatedBatchPlainLenChecked(items []bulkDedicatedSendRequest) (int, error) {
|
||||
if len(items) == 0 || len(items) > bulkDedicatedBatchMaxItems {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
total := bulkDedicatedBatchHeaderLen
|
||||
itemsLen, err := bulkDedicatedSendRequestsLenChecked(items)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if total > bulkDedicatedBatchMaxPlainBytes-itemsLen {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
return total + itemsLen, nil
|
||||
}
|
||||
|
||||
func bulkDedicatedSendRequestsLenChecked(items []bulkDedicatedSendRequest) (int, error) {
|
||||
total := 0
|
||||
for _, item := range items {
|
||||
itemLen := bulkDedicatedSendRequestLen(item)
|
||||
if itemLen < bulkDedicatedBatchItemHeaderLen || itemLen > bulkDedicatedBatchMaxPlainBytes {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
if total > bulkDedicatedBatchMaxPlainBytes-itemLen {
|
||||
return 0, errBulkFastPayloadInvalid
|
||||
}
|
||||
total += itemLen
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func encodeBulkDedicatedBatchesPlain(batches []bulkDedicatedOutboundBatch) ([]byte, error) {
|
||||
total, err := bulkDedicatedBatchesPlainLenChecked(batches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total := bulkDedicatedBatchesPlainLen(batches)
|
||||
buf := make([]byte, total)
|
||||
if err := writeBulkDedicatedBatchesPlain(buf, batches); err != nil {
|
||||
return nil, err
|
||||
@@ -552,7 +676,11 @@ func writeBulkDedicatedSuperBatchPlain(buf []byte, batches []bulkDedicatedOutbou
|
||||
if len(batches) <= 1 {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
if len(buf) != bulkDedicatedBatchesPlainLen(batches) || len(buf) > bulkDedicatedBatchMaxPlainBytes {
|
||||
plainLen, err := bulkDedicatedBatchesPlainLenChecked(batches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buf) != plainLen {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
copy(buf[:4], bulkDedicatedSuperBatchMagic)
|
||||
@@ -630,7 +758,10 @@ func encodeBulkDedicatedBatchesPayloadFast(encode transportFastPlainEncoder, sec
|
||||
if encode == nil {
|
||||
return nil, errTransportPayloadEncryptFailed
|
||||
}
|
||||
plainLen := bulkDedicatedBatchesPlainLen(batches)
|
||||
plainLen, err := bulkDedicatedBatchesPlainLenChecked(batches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encode(secretKey, plainLen, func(dst []byte) error {
|
||||
return writeBulkDedicatedBatchesPlain(dst, batches)
|
||||
})
|
||||
@@ -645,10 +776,14 @@ func bulkDedicatedBatchPlainLen(items []bulkDedicatedSendRequest) int {
|
||||
}
|
||||
|
||||
func writeBulkDedicatedBatchPlain(buf []byte, dataID uint64, items []bulkDedicatedSendRequest) error {
|
||||
if dataID == 0 || len(items) == 0 {
|
||||
if dataID == 0 {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
if len(buf) != bulkDedicatedBatchPlainLen(items) {
|
||||
plainLen, err := bulkDedicatedBatchPlainLenChecked(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buf) != plainLen {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
copy(buf[:4], bulkDedicatedBatchMagic)
|
||||
@@ -679,10 +814,11 @@ func decodeBulkDedicatedBatchPlain(payload []byte) (uint64, []bulkDedicatedBatch
|
||||
return 0, nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
dataID := binary.BigEndian.Uint64(payload[8:16])
|
||||
count := int(binary.BigEndian.Uint32(payload[16:20]))
|
||||
if dataID == 0 || count <= 0 {
|
||||
wireCount := binary.BigEndian.Uint32(payload[16:20])
|
||||
if dataID == 0 || wireCount == 0 || wireCount > bulkDedicatedBatchMaxItems {
|
||||
return 0, nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
count := int(wireCount)
|
||||
items := make([]bulkDedicatedBatchItem, 0, count)
|
||||
offset := bulkDedicatedBatchHeaderLen
|
||||
for i := 0; i < count; i++ {
|
||||
@@ -697,11 +833,12 @@ func decodeBulkDedicatedBatchPlain(payload []byte) (uint64, []bulkDedicatedBatch
|
||||
}
|
||||
flags := payload[offset+1]
|
||||
seq := binary.BigEndian.Uint64(payload[offset+4 : offset+12])
|
||||
dataLen := int(binary.BigEndian.Uint32(payload[offset+12 : offset+16]))
|
||||
wireDataLen := binary.BigEndian.Uint32(payload[offset+12 : offset+16])
|
||||
offset += bulkDedicatedBatchItemHeaderLen
|
||||
if dataLen < 0 || len(payload)-offset < dataLen {
|
||||
if uint64(wireDataLen) > uint64(len(payload)-offset) {
|
||||
return 0, nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
dataLen := int(wireDataLen)
|
||||
items = append(items, bulkDedicatedBatchItem{
|
||||
Type: itemType,
|
||||
Flags: flags,
|
||||
@@ -726,10 +863,11 @@ func decodeBulkDedicatedSuperBatchPlain(payload []byte) ([]bulkDedicatedInboundB
|
||||
if payload[4] != bulkDedicatedSuperBatchVersion {
|
||||
return nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
groupCount := int(binary.BigEndian.Uint32(payload[8:12]))
|
||||
if groupCount <= 0 {
|
||||
wireGroupCount := binary.BigEndian.Uint32(payload[8:12])
|
||||
if wireGroupCount == 0 || wireGroupCount > bulkDedicatedBatchMaxItems {
|
||||
return nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
groupCount := int(wireGroupCount)
|
||||
batches := make([]bulkDedicatedInboundBatch, 0, groupCount)
|
||||
offset := bulkDedicatedSuperBatchHeaderLen
|
||||
totalItems := 0
|
||||
@@ -738,11 +876,12 @@ func decodeBulkDedicatedSuperBatchPlain(payload []byte) ([]bulkDedicatedInboundB
|
||||
return nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
dataID := binary.BigEndian.Uint64(payload[offset : offset+8])
|
||||
count := int(binary.BigEndian.Uint32(payload[offset+8 : offset+12]))
|
||||
wireCount := binary.BigEndian.Uint32(payload[offset+8 : offset+12])
|
||||
offset += bulkDedicatedSuperBatchGroupHeaderLen
|
||||
if dataID == 0 || count <= 0 {
|
||||
if dataID == 0 || wireCount == 0 || wireCount > bulkDedicatedBatchMaxItems {
|
||||
return nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
count := int(wireCount)
|
||||
totalItems += count
|
||||
if totalItems > bulkDedicatedBatchMaxItems {
|
||||
return nil, true, errBulkFastPayloadInvalid
|
||||
@@ -760,11 +899,12 @@ func decodeBulkDedicatedSuperBatchPlain(payload []byte) ([]bulkDedicatedInboundB
|
||||
}
|
||||
flags := payload[offset+1]
|
||||
seq := binary.BigEndian.Uint64(payload[offset+4 : offset+12])
|
||||
dataLen := int(binary.BigEndian.Uint32(payload[offset+12 : offset+16]))
|
||||
wireDataLen := binary.BigEndian.Uint32(payload[offset+12 : offset+16])
|
||||
offset += bulkDedicatedBatchItemHeaderLen
|
||||
if dataLen < 0 || len(payload)-offset < dataLen {
|
||||
if uint64(wireDataLen) > uint64(len(payload)-offset) {
|
||||
return nil, true, errBulkFastPayloadInvalid
|
||||
}
|
||||
dataLen := int(wireDataLen)
|
||||
items = append(items, bulkDedicatedBatchItem{
|
||||
Type: itemType,
|
||||
Flags: flags,
|
||||
@@ -881,10 +1021,11 @@ func walkDedicatedBulkInboundBatchPlain(payload []byte, visit func(dataID uint64
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
dataID := binary.BigEndian.Uint64(payload[8:16])
|
||||
count := int(binary.BigEndian.Uint32(payload[16:20]))
|
||||
if dataID == 0 || count <= 0 {
|
||||
wireCount := binary.BigEndian.Uint32(payload[16:20])
|
||||
if dataID == 0 || wireCount == 0 || wireCount > bulkDedicatedBatchMaxItems {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
count := int(wireCount)
|
||||
offset := bulkDedicatedBatchHeaderLen
|
||||
for i := 0; i < count; i++ {
|
||||
if len(payload)-offset < bulkDedicatedBatchItemHeaderLen {
|
||||
@@ -898,11 +1039,12 @@ func walkDedicatedBulkInboundBatchPlain(payload []byte, visit func(dataID uint64
|
||||
}
|
||||
flags := payload[offset+1]
|
||||
seq := binary.BigEndian.Uint64(payload[offset+4 : offset+12])
|
||||
dataLen := int(binary.BigEndian.Uint32(payload[offset+12 : offset+16]))
|
||||
wireDataLen := binary.BigEndian.Uint32(payload[offset+12 : offset+16])
|
||||
offset += bulkDedicatedBatchItemHeaderLen
|
||||
if dataLen < 0 || len(payload)-offset < dataLen {
|
||||
if uint64(wireDataLen) > uint64(len(payload)-offset) {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
dataLen := int(wireDataLen)
|
||||
if err := visit(dataID, bulkDedicatedBatchItem{
|
||||
Type: itemType,
|
||||
Flags: flags,
|
||||
@@ -926,10 +1068,11 @@ func walkDedicatedBulkInboundSuperBatchPlain(payload []byte, visit func(dataID u
|
||||
if payload[4] != bulkDedicatedSuperBatchVersion {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
groupCount := int(binary.BigEndian.Uint32(payload[8:12]))
|
||||
if groupCount <= 0 {
|
||||
wireGroupCount := binary.BigEndian.Uint32(payload[8:12])
|
||||
if wireGroupCount == 0 || wireGroupCount > bulkDedicatedBatchMaxItems {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
groupCount := int(wireGroupCount)
|
||||
offset := bulkDedicatedSuperBatchHeaderLen
|
||||
totalItems := 0
|
||||
for i := 0; i < groupCount; i++ {
|
||||
@@ -937,11 +1080,12 @@ func walkDedicatedBulkInboundSuperBatchPlain(payload []byte, visit func(dataID u
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
dataID := binary.BigEndian.Uint64(payload[offset : offset+8])
|
||||
count := int(binary.BigEndian.Uint32(payload[offset+8 : offset+12]))
|
||||
wireCount := binary.BigEndian.Uint32(payload[offset+8 : offset+12])
|
||||
offset += bulkDedicatedSuperBatchGroupHeaderLen
|
||||
if dataID == 0 || count <= 0 {
|
||||
if dataID == 0 || wireCount == 0 || wireCount > bulkDedicatedBatchMaxItems {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
count := int(wireCount)
|
||||
totalItems += count
|
||||
if totalItems > bulkDedicatedBatchMaxItems {
|
||||
return errBulkFastPayloadInvalid
|
||||
@@ -958,11 +1102,12 @@ func walkDedicatedBulkInboundSuperBatchPlain(payload []byte, visit func(dataID u
|
||||
}
|
||||
flags := payload[offset+1]
|
||||
seq := binary.BigEndian.Uint64(payload[offset+4 : offset+12])
|
||||
dataLen := int(binary.BigEndian.Uint32(payload[offset+12 : offset+16]))
|
||||
wireDataLen := binary.BigEndian.Uint32(payload[offset+12 : offset+16])
|
||||
offset += bulkDedicatedBatchItemHeaderLen
|
||||
if dataLen < 0 || len(payload)-offset < dataLen {
|
||||
if uint64(wireDataLen) > uint64(len(payload)-offset) {
|
||||
return errBulkFastPayloadInvalid
|
||||
}
|
||||
dataLen := int(wireDataLen)
|
||||
if err := visit(dataID, bulkDedicatedBatchItem{
|
||||
Type: itemType,
|
||||
Flags: flags,
|
||||
|
||||
Reference in New Issue
Block a user