Skip to content

feat: FrameError 事件增补会话归属与快照完整性元数据(#56) - #57

Merged
CSJ608 merged 3 commits into
mainfrom
feat-frame-error-session-metadata
Aug 29, 2026
Merged

CSJ608 merged 3 commits into
mainfrom
feat-frame-error-session-metadata

Conversation

@CSJ608

@CSJ608 CSJ608 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

摘要

实现 #56:FrameError 事件增补会话归属与快照完整性元数据。评审意见见 issue #56 评审评论,本 PR 按评审结论实现(采纳首选方案:直接增补 FrameErrorEventArgs,否掉另开事件源的替代方案)。

API(源/二进制兼容的纯增量)

public sealed class FrameErrorEventArgs : EventArgs
{
    public long SessionId { get; }          // 检测会话编号,恒 > 0(连接发出的事件)
    public long ObservedByteCount { get; }  // 原始观测字节数,恒 >= Bytes.Length
    public bool IsTruncated { get; }        // 构造函数内自动计算 = Bytes.Length < ObservedByteCount
}
  • 旧构造重载保留,委托新重载(observedByteCount: bytes.Length → IsTruncated 恒 false),不变式自动成立。
  • SessionId 与 CurrentSessionId / SessionMessage.SessionId 同一编号空间,可跨 API 直接关联比对;事件参数在解码器内部构造、构造时绑定——重连后延迟到达的旧会话事件仍带旧编号(与消息通道的会话归属语义对齐,收口既有不一致)。
  • ObservedByteCount 含义按 Kind 分化(XML 文档附语义表):DecodeFailed=帧内负载、DiscardedByResync=被丢弃字节、Timeout/Overflow=半帧缓冲全长(含已收到的长度头)。

实现要点

  • FrameDecoder 四处事件构造传入 _sessionId(此前被丢弃)与内部已知的实际字节数(payload.Length / discarded.Length / pendingBytes / buffer.Length)——只在错误路径上携带,零热路径成本。
  • DecodeFailed / DiscardedByResync 契约如实公开"完整拷贝"现状(ObservedByteCount == Bytes.Length && !IsTruncated);timeout/overflow 超 8KB 快照上限时如实标记截断。

回归测试(98 → 101,×3 TFM 全绿)

  • 四类 Kind 全部断言会话归属 + 观测字节数 + 截断标记(解码器级 + 真实 TCP 端到端)。
  • 跨会话旧编号:双轮超时编号随会话递增(1→2);重连后旧事件编号仍为 1 而当前会话已为 2;DiscardedByResync(唯一不断开的 Kind)跨会话重建仍带来源编号;解码器级"编号构造时绑定"(旧解码器 7 的事件不因新解码器 8 存在而改写)。
  • Retry 窗口实证:DecodeFailed 断言点 CurrentSessionId 已归零而事件携带 1——正是 issue 指出"回调时刻读当前编号无法归属"的场景。
  • 截断:9KB 半帧 timeout(ObservedByteCount=9004、Bytes=8192、IsTruncated=true);overflow 超 8KB 上限同款断言;≤上限时全量且不截断。

验证

  • dotnet build StreamFrame.slnx -c Release(0 警告 0 错误)
  • dotnet test StreamFrame.slnx -c Release --no-build:net48 / net8.0 / net10.0 各 101 项通过

Closes #56

Marci added 3 commits August 29, 2026 21:57
- FrameErrorEventArgs 新增 SessionId / ObservedByteCount / IsTruncated:
  SessionId 与 CurrentSessionId/SessionMessage.SessionId 同一编号空间,
  事件在解码器内构造时绑定会话——重连后迟到的旧会话事件仍带旧编号;
  ObservedByteCount 恒 >= Bytes.Length(含义按 Kind 分化,XML 文档附语义表),
  IsTruncated 在构造函数内自动计算,杜绝与 ObservedByteCount 不一致
- FrameDecoder 四处事件构造传入 _sessionId 与实际观测字节数
  (DecodeFailed=负载长、DiscardedByResync=丢弃长、Timeout=pendingBytes、
  Overflow=buffer.Length)
- 旧构造重载保留并委托新重载(源/二进制兼容);README 双语示例同步
- FrameDecoderTests:既有四类 Kind 事件补 SessionId/ObservedByteCount/IsTruncated
  断言;新增 overflow 超 8KB 快照上限的截断上报(ObservedByteCount=9228、
  Bytes=8192 前缀);新增"会话编号构造时绑定"测试——旧解码器(7)的事件不因
  新解码器(8)存在而改写
- IncompleteFrameTimeoutTests:小半帧全量上报、9KB 半帧截断上报、双轮超时
  编号随会话递增(1→2);重连后断言旧事件编号仍为 1 而当前会话已为 2
- ConnectionResilienceTests:DecodeFailed 在 Retry 窗口断言事件编号为 1 而
  CurrentSessionId 已归零(回调时刻读当前编号无法归属的实证);SkipFrame
  同会话编号一致;overflow 全量上报 + Retry 窗口编号归零
- SessionAwareConnectionTests 新增:DiscardedByResync(唯一不断开的 Kind)
  跨会话重建,事件仍带来源会话编号(1 与 2),与当前会话严格区分
@CSJ608
CSJ608 merged commit fd42af7 into main Aug 29, 2026
5 checks passed
@CSJ608
CSJ608 deleted the feat-frame-error-session-metadata branch August 29, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 为 FrameError 提供原始会话归属与快照完整性元数据

1 participant