Repository navigation
[Runtime Architecture][Phase 09] 迁移 heartbeat、reconnect、retry、breaker、drain 与 shutdown 到统一时间源 #77
Description
总跟踪:#67
分析基线:main@201b1621cc706ee221e10ac20947af5fc62b087e
阶段依赖:Phase 08 #76
建议交付:一个聚焦 PR;可拆为多个 commit,但合并时不得保留新旧双路径。
Use case
Phase 08 只统一调用 deadline 和两个 scheduler;Heartbeat、Reconnect、Retry、Circuit Breaker、DynamicModule drain、Server cleanup budget 等仍散落使用 Task.Delay、Stopwatch、UTC、Timer 或 CancelAfter。它们必须共享 RuntimeContext monotonic time,才能消除 wall-clock 影响并实现全链路确定测试。
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 前后数据。
- 每个新增可变状态都回答四个问题。 谁创建?谁拥有?哪个终态释放?停止/失败时如何被等待和观察?无法用一两句话回答时不得合并。
本阶段任务
- 逐 subsystem 迁移 heartbeat send/check 与 timeout、reconnect backoff/jitter、retry delay/deadline gate、circuit breaker open/half-open、admission delay gate、dynamic module drain、graceful shutdown/framework cleanup budget。
- 所有 elapsed/deadline/budget 计算使用 RuntimeContext TimeProvider monotonic timestamp;Task scheduling 使用 provider-aware delay/timer。
- 复用 Phase 08 的同一
RpcDeadline,Retry/Admission 在开始等待前判断是否会越过 deadline,不能创建第二套剩余时间。 - 对每个 subsystem 建立 fake-time tests:推进到边界前/边界/边界后,取消与 stop 竞争,多实例不同 provider 隔离。
- 保留 System TimeProvider integration/stress,确认真实 timer、线程调度和 stop 顺序。
- 评估 reconnect jitter 与 P2C
Random.Shared;仅当测试可重复性确有需要时引入极小 internalIRandomSource,不做通用随机 service locator,也不进入 codec/protocol 热路径。 - 全仓库审计 local timeout 对
DateTimeOffset.UtcNow、Stopwatch.GetTimestamp、裸Task.Delay、new Timer、CancelAfter的剩余引用并分类;wire/diagnostic 合理引用保留并注明。
交付物
- Runtime 子系统统一 monotonic timing implementation。
- 全仓 timing reference 审计与允许项清单。
- Heartbeat/Retry/Breaker/Drain/Shutdown fake-time 与 System integration tests。
验收标准
- 本地 deadline、heartbeat、retry、breaker、drain、shutdown 不依赖 wall clock。
- 每类子系统至少一组 fake-time 确定测试,并有 System provider 集成覆盖。
- 多 RuntimeContext 的时间推进互不影响。
- 允许保留的 UTC/Stopwatch/Delay/Timer 引用都有 wire/diagnostic/平台理由。
- 无新的无界 backoff/retry 或未监督 timer/task。
本阶段补充验证
- Heartbeat、Reconnect、Retry、Breaker、DynamicModule、Server stop 单测与 integration/chaos。
- 对频繁 elapsed/check 路径提交 allocation、throughput、p99 对比。
通用验证门禁
基础门禁(按本阶段影响范围运行,并在 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
不改变 wire deadline、retry public policy 或 breaker externally documented semantics,除非修复已有 wall-clock bug并在迁移文档说明。随机源若加入保持 internal。
风险与回滚
风险是 fake time 与真实 scheduler 差异、停止预算语义变化。按 subsystem 小步迁移并保留 System tests;可回滚单一 subsystem,但不恢复 UTC 作为本地 deadline 基准。
Out of scope
不重写 retry/breaker 算法,不把 TimeProvider 查询放进每帧或每流 item 热路径。
Alternatives considered
不采用长期保留 legacy constructor、obsolete forwarding overload、Default Context、Runtime reflection adapter、旧 Generated ABI adapter、无界任务/队列或全局锁的方案。若真实扩展需求出现,应设计窄而稳定的新 SPI;若阶段实现需要扩大到无关 package/子系统,应另开 issue 并给出可验证收益。
Checks
- 已搜索现有 Issues 与当前文档,未发现同一实施阶段的重复 issue。
- 本 issue 保留了原实施方案的阶段任务、通用原则、验收、兼容性、性能和回滚约束,并补充了可执行细节。
Activity
SunSi12138 commentedon Aug 10, 2026
已由 #118 完整实现并合并到 dev(merge commit b44b735)。统一 TimeProvider 迁移、确定性边界测试、构建/生成器/集成/AOT/打包/负载与 Chaos 门禁均已通过。