Repository navigation
[Runtime Architecture][Phase 05] 以 NegotiatedSessionOptions 一次性发布握手结果 #73
Description
总跟踪:#67
分析基线:main@201b1621cc706ee221e10ac20947af5fc62b087e
阶段依赖:Phase 02 #70
建议交付:一个聚焦 PR;可拆为多个 commit,但合并时不得保留新旧双路径。
Use case
capability、frame limit、flow-control window、compression binding 当前通过多个字段/setter 逐项写入。新增 capability 时,Client/Server 多分支必须以正确顺序设置全部字段,容易出现 Ready 已发布但 negotiated state 不完整的组合。
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 前后数据。
- 每个新增可变状态都回答四个问题。 谁创建?谁拥有?哪个终态释放?停止/失败时如何被等待和观察?无法用一两句话回答时不得合并。
本阶段任务
- 引入 immutable
NegotiatedSessionOptions,至少包含 protocol minor、capabilities、max frame payload、stream/connection receive window、compression binding。 - Session 仅暴露 internal
TryCompleteHandshake(NegotiatedSessionOptions):验证不超过 local limit、配置 flow controller、固定 compression、原子发布 snapshot、Handshaking -> Ready,并拒绝二次发布。 - 热路径通过一次
Volatile.Read(ref _negotiated)获得完整快照;如需优化,在握手后由 SendPump/Parser 缓存引用,不得重新拆回多组 mutable 字段。 - 迁移期间可短暂保留旧字段,但必须只由 snapshot 填充并有一致性断言;PR 最终删除 negotiated setter 和双写路径。
- 集中 phase/frame eligibility:Handshaking 仅 handshake/error;Ready 允许业务与控制帧;Draining 禁止新 Request 但保留已有调用所需 Response/Stream/Cancel/WindowUpdate/Heartbeat/GoAway;Stopping/Terminal 只清理。
- 握手失败不得发布 Ready;第二次 response、畸形/越界参数和 compression/flow 初始化失败必须有确定终态。
交付物
NegotiatedSessionOptions与一次性 handshake completion API。- 集中 phase/frame matrix 和删除零散 setter。
- negotiated publication、失败和二次发布测试。
验收标准
- negotiated state 只发布一次,Ready 可见时 snapshot 必然完整且不可变。
- flow controller 与 compression binding 只由 handshake completion 固定。
- 二次发布、越界参数和握手失败均不会进入 Ready。
- Draining 禁止新 Request,但已有调用必须的控制帧仍可发送/接收。
- 无 negotiated 零散 setter 或双写兼容路径残留。
本阶段补充验证
- capability/frame/window/compression 组合与二次发布 race。
- Protocol v2 golden、SendPump、StreamManager、RpcSession lifecycle 与 integration tests。
- 基准验证 snapshot 读取没有每帧 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
不改变 Protocol v2 wire;这是 Runtime 内部状态发布方式变化。任何 public/internal setter 删除都不提供 shim。新增 snapshot 不得产生每帧 allocation。
风险与回滚
风险是改变 handshake 时序或漏掉某个能力字段。先用现有算法组装 snapshot,再删除 setter;golden/integration tests 锁定 wire。出现问题可回滚本 PR,不回到长期双写。
Out of scope
不集中 Client/Server 协商算法(Phase 06),只建立唯一结果与发布边界。
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
Implemented by #108 and merged into dev at c8e3863. Ubuntu bare-metal validation, Quick, CodeQL, and the exact-head cross-platform Release Gate all passed.