总跟踪:#67
分析基线:main @ 201b1621cc706ee221e10ac20947af5fc62b087e
阶段依赖:Phase 02 #70、Phase 07 #75
建议交付:一个聚焦 PR;可拆为多个 commit,但合并时不得保留新旧双路径。
Use case
Logical invocation、physical attempt、Session active request、Server connection/global calls、DynamicModule generation 与 stream dispatch 分别计数,但 scope、increment owner 和 terminal owner 缺少统一矩阵。重复 counter 会在 cancel/response/deadline/disconnect/GoAway 竞态下 underflow 或阻塞 drain。
Proposed behavior
本阶段按下述工程原则和任务清单实施。目标不是增加抽象数量,而是减少非法状态、所有权分支、并发状态组合与测试不确定性。
通用工程原则(本阶段必须遵守)
- 让非法状态不可表示。 构造完成的对象必须立即合法;握手结果、BuildPlan、拓扑和其他稳定配置使用 immutable snapshot 一次发布,不依赖调用顺序完成二次绑定。
- 单一所有权、单一终态。 每个 transport、counter、lease、timer、后台任务和构建期资源都必须写明谁创建、谁拥有、哪个终态释放、停止或失败时由谁等待/观察;竞争终态必须由同一个 winner 收敛,禁止重复释放和 underflow。
- 分离状态域。 Session 的 transport/protocol 状态、ClientConnection 的新调用资格、ServerConnectionState 的认证/调用排空状态分别建模,不用一个布尔值或一个巨型状态机互相冒充。
- 并发必须有界且可监督。 不引入无界队列、无限重试或无人等待的 fire-and-forget;framework-owned detached task 必须登记、观察异常,并在 owner 停止时可 drain。批量 drain 前先关闭 owner-level 写入门槛。
- 统一时间语义。 UTC 只服务 wire deadline、日志、Activity 和公开诊断;本地 deadline、elapsed、heartbeat、retry、breaker、drain 和 shutdown budget 使用同一
TimeProvider 的 monotonic timestamp,并能用 fake time 确定测试。
- 资源构建是事务。 Builder 先编译完全验证且不可变的 plan,再 materialize resource,最后 commit 所有权;失败时保留 primary exception,并按登记逆序恰好清理一次。Caller-owned 资源不得被框架擅自释放。
- 保护热路径。 继续读取 immutable snapshot,不增加全局锁;supervisor、BuildPlan、ManifestSource 查询不得进入每次 RPC/每帧/每 item 路径;不得引入无依据的每调用 allocation 或新的锁竞争。
- 明确兼容性边界。 默认保持 Protocol v2 wire format、Contract ID/Method ID/DTO member ID 与 schema 规则;CLR/source API 和 Generated ABI 可以 breaking,但必须显式升级版本、注册/启动阶段 fail fast,且不保留 legacy constructor、Default Context、reflection adapter 或旧 ABI 转接层。
- 增量实施、独立验证和回滚。 每个阶段对应一个聚焦 PR,可单独测试、基准和回滚;不一次性重写 Runtime,也不为了目录整齐或抽象形式扩大范围。
- 保留已经正确的并发模式。 例如
StreamManager.CompleteAll 先 CAS 发布终态再逐 stripe drain、PendingCall 单槽位终态竞争、dispatcher acquire/close/detach lease;不得以“统一”为名改成全局锁或 ConcurrentDictionary 全面替换。
- 测试与诊断先行。 生命周期变更必须有竞态、fault-injection、fake-time、stress/chaos、ALC/NativeAOT(按影响范围)测试;触及热路径时必须提交 BenchmarkDotNet 前后数据。
- 每个新增可变状态都回答四个问题。 谁创建?谁拥有?哪个终态释放?停止/失败时如何被等待和观察?无法用一两句话回答时不得合并。
本阶段任务
- 建立并落入代码/文档的 ownership matrix:LogicalInvocationCount、PhysicalConnectionCallCount、可能的 SessionActiveRequestCount、ServerConnectionActiveCalls、ServerGlobalActiveCalls、DynamicModuleCall/StreamLease、StreamDispatchLease。
- 对每个 counter 列出 scope、增加点、唯一终态释放方、drain/selection/metrics 用途;全仓追踪调用点和异常路径。
- 审计
RpcSession._activeRequests:若无独立于 ClientConnection/Server connection 的协议语义则删除;若保留则按真实含义改名并添加 invariant。
- 对同步 acquire/finally 使用 allocation-free readonly struct lease;多终态 PendingCall 继续 owner callback;DynamicModule lease 保留。不要为语法统一引入 per-call class allocation。
- 增加状态映射断言(只在 transition/test/debug 边界):ClientConnection.Ready => Session.Ready && CanAcceptCalls;Server Ready => authenticated && negotiated;Session Stopping => send pump stop requested && stream termination published 等。
- 通过 Phase 07 supervisor/stop 顺序验证 draining:新调用不再进入 ready snapshot,既有 Response/Stream/WindowUpdate/Cancel 能完成。
- 保留 StreamManager CAS terminal barrier + striped drain、dispatcher lease、late frame 有界统计;不引入跨 stripe 总锁。
- 建立 response/cancel/deadline/disconnect/GoAway 与 module replace/unregister 的全排列或系统化 race/chaos 测试。
交付物
- 可执行的 counter/lease ownership matrix。
- 重复 counter 删除或语义化重命名及状态 invariant helpers。
- Call accounting/drain/module/stream race 测试。
验收标准
本阶段补充验证
- Call accounting race/chaos、multi-cluster/dynamic module、graceful drain、StreamManager tests。
- Pending/connection selection/stream dispatch 基准,验证无 allocation 与锁竞争回退。
通用验证门禁
基础门禁(按本阶段影响范围运行,并在 PR 中记录命令与结果):
dotnet build Sharplink.slnx -c Release
dotnet test --project test/SharpLink.UnitTests/SharpLink.UnitTests.csproj -c Release
dotnet test --project test/SharpLink.Generator.Tests/SharpLink.Generator.Tests.csproj -c Release
dotnet run --project test/SharpLink.IntegrationTests/SharpLink.IntegrationTests.csproj -c Release -- --timeout 120s
触及 Session、Transport、TimeProvider、DynamicModule 或 generated ABI 时,补充相关的 SharpLink.AotSmoke、SharpLink.ChaosTests、TransportConnectionIntegrationTests、RuntimeAssemblyIntegrationTests、RuntimeMultiClusterIntegrationTests、SendPumpTests、RpcSessionLifecycleTests、StreamManagerTests。
若改动进入 Unary 调用、PendingRequestTable、StreamManager、SendPump、P2C 或 codec resolve 热路径,必须提供 BenchmarkDotNet 前后对比:不得无说明增加每调用 allocation;吞吐回退超过基准噪声(CI 稳定前人工参考 3%–5%)时阻塞;p99 不得出现由新抽象引起的锁竞争。
Compatibility and performance impact
不改变 RPC wire 或逻辑/物理指标定义,除非 issue 明确记录重复/错误计数修复。删除 counter 前必须证明无独立语义;不接受“保险起见”保留两套。
风险与回滚
错删 counter 可能破坏 P2C、limit 或 drain。删除前记录依赖并用 stress/chaos 证明;可恢复某一具体 counter,但必须补齐独立语义,不能无说明复制计数。
Out of scope
consumer-abandoned busy polling 的信号实现由 Phase 14;不重写有界表/分片容器。
Alternatives considered
不采用长期保留 legacy constructor、obsolete forwarding overload、Default Context、Runtime reflection adapter、旧 Generated ABI adapter、无界任务/队列或全局锁的方案。若真实扩展需求出现,应设计窄而稳定的新 SPI;若阶段实现需要扩大到无关 package/子系统,应另开 issue 并给出可验证收益。
Checks
Use case
Logical invocation、physical attempt、Session active request、Server connection/global calls、DynamicModule generation 与 stream dispatch 分别计数,但 scope、increment owner 和 terminal owner 缺少统一矩阵。重复 counter 会在 cancel/response/deadline/disconnect/GoAway 竞态下 underflow 或阻塞 drain。
Proposed behavior
本阶段按下述工程原则和任务清单实施。目标不是增加抽象数量,而是减少非法状态、所有权分支、并发状态组合与测试不确定性。
通用工程原则(本阶段必须遵守)
TimeProvider的 monotonic timestamp,并能用 fake time 确定测试。StreamManager.CompleteAll先 CAS 发布终态再逐 stripe drain、PendingCall单槽位终态竞争、dispatcher acquire/close/detach lease;不得以“统一”为名改成全局锁或ConcurrentDictionary全面替换。本阶段任务
RpcSession._activeRequests:若无独立于 ClientConnection/Server connection 的协议语义则删除;若保留则按真实含义改名并添加 invariant。交付物
验收标准
本阶段补充验证
通用验证门禁
基础门禁(按本阶段影响范围运行,并在 PR 中记录命令与结果):
触及 Session、Transport、TimeProvider、DynamicModule 或 generated ABI 时,补充相关的
SharpLink.AotSmoke、SharpLink.ChaosTests、TransportConnectionIntegrationTests、RuntimeAssemblyIntegrationTests、RuntimeMultiClusterIntegrationTests、SendPumpTests、RpcSessionLifecycleTests、StreamManagerTests。若改动进入 Unary 调用、PendingRequestTable、StreamManager、SendPump、P2C 或 codec resolve 热路径,必须提供 BenchmarkDotNet 前后对比:不得无说明增加每调用 allocation;吞吐回退超过基准噪声(CI 稳定前人工参考 3%–5%)时阻塞;p99 不得出现由新抽象引起的锁竞争。
Compatibility and performance impact
不改变 RPC wire 或逻辑/物理指标定义,除非 issue 明确记录重复/错误计数修复。删除 counter 前必须证明无独立语义;不接受“保险起见”保留两套。
风险与回滚
错删 counter 可能破坏 P2C、limit 或 drain。删除前记录依赖并用 stress/chaos 证明;可恢复某一具体 counter,但必须补齐独立语义,不能无说明复制计数。
Out of scope
consumer-abandoned busy polling 的信号实现由 Phase 14;不重写有界表/分片容器。
Alternatives considered
不采用长期保留 legacy constructor、obsolete forwarding overload、Default Context、Runtime reflection adapter、旧 Generated ABI adapter、无界任务/队列或全局锁的方案。若真实扩展需求出现,应设计窄而稳定的新 SPI;若阶段实现需要扩大到无关 package/子系统,应另开 issue 并给出可验证收益。
Checks