feat: 完善 RecordStream 的协议协商、运行观测与文档说明
- 将 RecordStream 出站路径收敛为单 writer loop - 支持在 batch header 中 piggyback AckSeq,保留独立 ack 作为兼容回退 - 增加 record stream 打开阶段能力协商,支持 mixed-version peer 自动降级 - 补充 RecordSnapshot 与 diagnostics summary 的 record-plane 观测项 - 增加 batch/ack/error frame、piggyback ack、barrier 等待拆分与 apply backlog 指标 - 收紧 TransportConn detach 后的 runtime snapshot 语义 - 补充 README 中的 RecordStream 语义、兼容行为与诊断快照说明 - 补充相关单测与 race 回归验证
This commit is contained in:
@@ -20,7 +20,7 @@ func TestGetClientDiagnosticsSnapshotDefaults(t *testing.T) {
|
||||
if got, want := snapshot.Runtime.OwnerState, "idle"; got != want {
|
||||
t.Fatalf("Runtime.OwnerState = %q, want %q", got, want)
|
||||
}
|
||||
if len(snapshot.Streams) != 0 || len(snapshot.Bulks) != 0 || len(snapshot.Transfers) != 0 {
|
||||
if len(snapshot.Streams) != 0 || len(snapshot.Bulks) != 0 || len(snapshot.Records) != 0 || len(snapshot.Transfers) != 0 {
|
||||
t.Fatalf("default diagnostics should be empty: %+v", snapshot)
|
||||
}
|
||||
if snapshot.Summary != (DiagnosticsSummary{}) {
|
||||
@@ -130,6 +130,137 @@ func TestGetClientDiagnosticsSnapshotAggregatesActiveState(t *testing.T) {
|
||||
_ = bulk.Close()
|
||||
}
|
||||
|
||||
func TestGetDiagnosticsSnapshotAggregatesActiveRecordState(t *testing.T) {
|
||||
server := NewServer().(*ServerCommon)
|
||||
if err := UseModernPSKServer(server, integrationSharedSecret, integrationModernPSKOptions()); err != nil {
|
||||
t.Fatalf("UseModernPSKServer failed: %v", err)
|
||||
}
|
||||
|
||||
recordAcceptCh := make(chan RecordAcceptInfo, 1)
|
||||
recordReleaseCh := make(chan struct{})
|
||||
recordHandlerDone := make(chan error, 1)
|
||||
server.SetRecordStreamHandler(func(info RecordAcceptInfo) error {
|
||||
recordAcceptCh <- info
|
||||
msg, err := info.RecordStream.ReadRecord(context.Background())
|
||||
if err != nil {
|
||||
recordHandlerDone <- err
|
||||
return err
|
||||
}
|
||||
if string(msg.Payload) != "diag-record" {
|
||||
err = errors.New("unexpected record payload")
|
||||
recordHandlerDone <- err
|
||||
return err
|
||||
}
|
||||
if err := info.RecordStream.AckRecord(msg.Seq); err != nil {
|
||||
recordHandlerDone <- err
|
||||
return err
|
||||
}
|
||||
<-recordReleaseCh
|
||||
err = info.RecordStream.Close()
|
||||
recordHandlerDone <- err
|
||||
return err
|
||||
})
|
||||
|
||||
if err := server.Listen("tcp", "127.0.0.1:0"); err != nil {
|
||||
t.Fatalf("server Listen failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = server.Stop()
|
||||
}()
|
||||
|
||||
client := NewClient().(*ClientCommon)
|
||||
if err := UseModernPSKClient(client, integrationSharedSecret, integrationModernPSKOptions()); err != nil {
|
||||
t.Fatalf("UseModernPSKClient failed: %v", err)
|
||||
}
|
||||
if err := client.Connect("tcp", server.listener.Addr().String()); err != nil {
|
||||
t.Fatalf("client Connect failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = client.Stop()
|
||||
}()
|
||||
|
||||
record, err := client.OpenRecordStream(context.Background(), RecordOpenOptions{
|
||||
Stream: StreamOpenOptions{ID: "diag-client-record"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("client OpenRecordStream failed: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-recordAcceptCh:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("timed out waiting accepted record stream")
|
||||
}
|
||||
|
||||
if _, err := record.WriteRecord(context.Background(), []byte("diag-record")); err != nil {
|
||||
t.Fatalf("WriteRecord failed: %v", err)
|
||||
}
|
||||
if _, err := record.Barrier(context.Background()); err != nil {
|
||||
t.Fatalf("Barrier failed: %v", err)
|
||||
}
|
||||
|
||||
clientSnapshot, err := GetClientDiagnosticsSnapshot(client)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientDiagnosticsSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := len(clientSnapshot.Records), 1; got != want {
|
||||
t.Fatalf("client record snapshot count = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := clientSnapshot.Summary.RecordCount, 1; got != want {
|
||||
t.Fatalf("client RecordCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := clientSnapshot.Summary.ActiveRecordCount, 1; got != want {
|
||||
t.Fatalf("client ActiveRecordCount = %d, want %d", got, want)
|
||||
}
|
||||
clientRecord := clientSnapshot.Records[0]
|
||||
if got := clientRecord.BatchFramesSent; got < 1 {
|
||||
t.Fatalf("client BatchFramesSent = %d, want >= 1", got)
|
||||
}
|
||||
if got := clientRecord.AckFramesReceived; got < 1 {
|
||||
t.Fatalf("client AckFramesReceived = %d, want >= 1", got)
|
||||
}
|
||||
if got := clientRecord.BarrierCount; got < 1 {
|
||||
t.Fatalf("client BarrierCount = %d, want >= 1", got)
|
||||
}
|
||||
if got := clientSnapshot.Summary.RecordTelemetry.FrameSendCount; got < 1 {
|
||||
t.Fatalf("client RecordTelemetry.FrameSendCount = %d, want >= 1", got)
|
||||
}
|
||||
|
||||
serverSnapshot, err := GetServerDiagnosticsSnapshot(server)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerDiagnosticsSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := len(serverSnapshot.Records), 1; got != want {
|
||||
t.Fatalf("server record snapshot count = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := serverSnapshot.Summary.RecordCount, 1; got != want {
|
||||
t.Fatalf("server RecordCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := serverSnapshot.Summary.ActiveRecordCount, 1; got != want {
|
||||
t.Fatalf("server ActiveRecordCount = %d, want %d", got, want)
|
||||
}
|
||||
serverRecord := serverSnapshot.Records[0]
|
||||
if got := serverRecord.BatchFramesReceived; got < 1 {
|
||||
t.Fatalf("server BatchFramesReceived = %d, want >= 1", got)
|
||||
}
|
||||
if got := serverRecord.AckFramesSent; got < 1 {
|
||||
t.Fatalf("server AckFramesSent = %d, want >= 1", got)
|
||||
}
|
||||
if got := serverSnapshot.Summary.RecordTelemetry.FrameReceiveCount; got < 1 {
|
||||
t.Fatalf("server RecordTelemetry.FrameReceiveCount = %d, want >= 1", got)
|
||||
}
|
||||
|
||||
close(recordReleaseCh)
|
||||
select {
|
||||
case err := <-recordHandlerDone:
|
||||
if err != nil {
|
||||
t.Fatalf("record handler failed: %v", err)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("timed out waiting record handler completion")
|
||||
}
|
||||
_ = record.Close()
|
||||
}
|
||||
|
||||
func TestGetServerDiagnosticsSnapshotAggregatesStaleAndResetState(t *testing.T) {
|
||||
server := NewServer().(*ServerCommon)
|
||||
|
||||
@@ -323,6 +454,127 @@ func TestDiagnosticsSummaryClassifiesResetCauses(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiagnosticsSummaryAggregatesRecordTelemetry(t *testing.T) {
|
||||
summary := summarizeClientDiagnosticsSnapshot(ClientDiagnosticsSnapshot{
|
||||
Records: []RecordSnapshot{
|
||||
{
|
||||
ID: "record-active",
|
||||
BindingCurrent: true,
|
||||
TransportAttached: true,
|
||||
TransportCurrent: true,
|
||||
OutstandingRecords: 3,
|
||||
OutstandingBytes: 4096,
|
||||
PendingApplyRecords: 2,
|
||||
PendingAckRecords: 1,
|
||||
PeakPendingApplyRecords: 5,
|
||||
BatchFramesSent: 10,
|
||||
AckFramesSent: 4,
|
||||
ErrorFramesSent: 1,
|
||||
BatchFramesReceived: 8,
|
||||
AckFramesReceived: 3,
|
||||
ErrorFramesReceived: 0,
|
||||
PiggybackAckSent: 6,
|
||||
PiggybackAckReceived: 2,
|
||||
BarrierCount: 4,
|
||||
BarrierFlushWaitDuration: 10 * time.Millisecond,
|
||||
BarrierApplyWaitDuration: 30 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
ID: "record-reset",
|
||||
ResetError: errTransportDetached.Error(),
|
||||
OutstandingRecords: 1,
|
||||
OutstandingBytes: 512,
|
||||
PendingApplyRecords: 3,
|
||||
PendingAckRecords: 2,
|
||||
PeakPendingApplyRecords: 7,
|
||||
BatchFramesSent: 2,
|
||||
AckFramesSent: 1,
|
||||
ErrorFramesSent: 1,
|
||||
BatchFramesReceived: 1,
|
||||
AckFramesReceived: 1,
|
||||
ErrorFramesReceived: 1,
|
||||
PiggybackAckSent: 1,
|
||||
PiggybackAckReceived: 1,
|
||||
BarrierCount: 1,
|
||||
BarrierFlushWaitDuration: 5 * time.Millisecond,
|
||||
BarrierApplyWaitDuration: 15 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if got, want := summary.RecordCount, 2; got != want {
|
||||
t.Fatalf("RecordCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := summary.ActiveRecordCount, 1; got != want {
|
||||
t.Fatalf("ActiveRecordCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := summary.ResetRecordCount, 1; got != want {
|
||||
t.Fatalf("ResetRecordCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := summary.RecordResetCauses.Total, 1; got != want {
|
||||
t.Fatalf("RecordResetCauses.Total = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := summary.RecordResetCauses.TransportDetached, 1; got != want {
|
||||
t.Fatalf("RecordResetCauses.TransportDetached = %d, want %d", got, want)
|
||||
}
|
||||
|
||||
telemetry := summary.RecordTelemetry
|
||||
if got, want := telemetry.BatchFramesSent, int64(12); got != want {
|
||||
t.Fatalf("BatchFramesSent = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.AckFramesSent, int64(5); got != want {
|
||||
t.Fatalf("AckFramesSent = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.ErrorFramesSent, int64(2); got != want {
|
||||
t.Fatalf("ErrorFramesSent = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.BatchFramesReceived, int64(9); got != want {
|
||||
t.Fatalf("BatchFramesReceived = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.AckFramesReceived, int64(4); got != want {
|
||||
t.Fatalf("AckFramesReceived = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.ErrorFramesReceived, int64(1); got != want {
|
||||
t.Fatalf("ErrorFramesReceived = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.FrameSendCount, int64(19); got != want {
|
||||
t.Fatalf("FrameSendCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.FrameReceiveCount, int64(14); got != want {
|
||||
t.Fatalf("FrameReceiveCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.PiggybackAckSent, int64(7); got != want {
|
||||
t.Fatalf("PiggybackAckSent = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.PiggybackAckReceived, int64(3); got != want {
|
||||
t.Fatalf("PiggybackAckReceived = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.BarrierCount, int64(5); got != want {
|
||||
t.Fatalf("BarrierCount = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.BarrierFlushWaitDuration, 15*time.Millisecond; got != want {
|
||||
t.Fatalf("BarrierFlushWaitDuration = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := telemetry.BarrierApplyWaitDuration, 45*time.Millisecond; got != want {
|
||||
t.Fatalf("BarrierApplyWaitDuration = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := telemetry.OutstandingRecords, 4; got != want {
|
||||
t.Fatalf("OutstandingRecords = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.OutstandingBytes, 4608; got != want {
|
||||
t.Fatalf("OutstandingBytes = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.PendingApplyRecords, 5; got != want {
|
||||
t.Fatalf("PendingApplyRecords = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.PendingAckRecords, 3; got != want {
|
||||
t.Fatalf("PendingAckRecords = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := telemetry.PeakPendingApplyRecords, 7; got != want {
|
||||
t.Fatalf("PeakPendingApplyRecords = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiagnosticsSummaryAggregatesTransferTelemetry(t *testing.T) {
|
||||
summary := summarizeClientDiagnosticsSnapshot(ClientDiagnosticsSnapshot{
|
||||
Transfers: []TransferSnapshot{
|
||||
|
||||
Reference in New Issue
Block a user