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
+91 -3
View File
@@ -166,6 +166,92 @@ func TestRecordStreamPropagatesStructuredFailure(t *testing.T) {
}
}
func TestRecordStreamPropagatesStructuredFailureUnderReceiveBackpressure(t *testing.T) {
server := NewServer().(*ServerCommon)
if err := UseModernPSKServer(server, integrationSharedSecret, integrationModernPSKOptions()); err != nil {
t.Fatal(err)
}
accepted := make(chan RecordStream, 1)
server.SetRecordStreamHandler(func(info RecordAcceptInfo) error {
accepted <- info.RecordStream
return nil
})
if err := server.Listen("tcp", "127.0.0.1:0"); err != nil {
t.Fatal(err)
}
defer server.Stop()
client := NewClient().(*ClientCommon)
if err := UseModernPSKClient(client, integrationSharedSecret, integrationModernPSKOptions()); err != nil {
t.Fatal(err)
}
if err := client.Connect("tcp", server.listener.Addr().String()); err != nil {
t.Fatal(err)
}
defer client.Stop()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
local, err := client.OpenRecordStream(ctx, RecordOpenOptions{InboundQueueLimit: 1})
if err != nil {
t.Fatal(err)
}
var remote RecordStream
select {
case remote = <-accepted:
case <-ctx.Done():
t.Fatal(ctx.Err())
}
seq, err := local.WriteRecord(ctx, []byte("request"))
if err != nil {
t.Fatal(err)
}
if err := local.Flush(ctx); err != nil {
t.Fatal(err)
}
request, err := remote.ReadRecord(ctx)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 2; i++ {
if _, err := remote.WriteRecord(ctx, []byte("pending response")); err != nil {
t.Fatal(err)
}
}
if err := remote.Flush(ctx); err != nil {
t.Fatal(err)
}
record := local.(*recordStream)
for {
record.mu.Lock()
received := record.inboundReceivedSeq
record.mu.Unlock()
if received == 2 && len(record.recvCh) == 1 {
break
}
select {
case <-ctx.Done():
t.Fatal("record receive queue did not fill")
case <-time.After(time.Millisecond):
}
}
want := RecordFailure{FailedSeq: request.Seq, Code: "disk_full", Retryable: true, Message: "disk full"}
if err := remote.FailRecord(request.Seq, want); err != nil {
t.Fatalf("FailRecord failed: %v", err)
}
select {
case <-local.Context().Done():
case <-ctx.Done():
t.Fatal("remote failure did not terminate local record stream")
}
_, err = local.BarrierTo(ctx, seq)
var got RecordFailure
if !errors.As(err, &got) || got != want {
t.Fatalf("structured failure lost under receive backpressure: got=%T %v, want=%+v", err, err, want)
}
}
func TestRecordStreamBackpressureUsesUnackedRecords(t *testing.T) {
server := NewServer().(*ServerCommon)
secret := []byte("0123456789abcdef0123456789abcdef")
@@ -340,7 +426,9 @@ func TestRecordStreamConcurrentWritesStayOrdered(t *testing.T) {
server.SetSecretKey(secret)
})
const total = 64
const total = 512
writeCtx, cancelWrites := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelWrites()
receivedCh := make(chan RecordMessage, total)
handlerDone := make(chan error, 1)
server.SetRecordStreamHandler(func(info RecordAcceptInfo) error {
@@ -387,14 +475,14 @@ func TestRecordStreamConcurrentWritesStayOrdered(t *testing.T) {
go func() {
defer wg.Done()
payload := []byte("item-" + strconv.Itoa(index))
if _, err := stream.WriteRecord(context.Background(), payload); err != nil {
if _, err := stream.WriteRecord(writeCtx, payload); err != nil {
t.Errorf("WriteRecord(%d) failed: %v", index, err)
}
}()
}
wg.Wait()
if acked, err := stream.Barrier(context.Background()); err != nil {
if acked, err := stream.Barrier(writeCtx); err != nil {
t.Fatalf("Barrier failed: %v", err)
} else if got, want := acked, uint64(total); got != want {
t.Fatalf("Barrier acked=%d want=%d", got, want)