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:
+91
-3
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user