Skip to content

feat(client): v0.7.10 多 Cluster Contract 路由与静态/动态程序集注册 #10

Description

@SunSi12138

背景

SharpLink dev 分支当前版本为 0.7.9。现有客户端拓扑模型支持:

  • 固定单端点,并保留专门的 fast path。
  • 一个静态 endpoint cluster,cluster 内可以有多个对等 endpoint。
  • 一个 resolver 驱动的动态 endpoint cluster。
  • cluster 内负载均衡、Retry、Endpoint Admission、Circuit Breaker、连接排空和动态拓扑更新。
  • Source Generator 生成的程序集 Manifest。
  • Client/Server 实例级的运行时程序集注册、替换、排空、注销和 collectible AssemblyLoadContext 释放。

当前缺口是:一个应用进程中的同一个逻辑 RPC 客户端,无法同时连接多个彼此独立的服务 Cluster,并按照 Contract 将调用固定路由到正确的 Cluster。例如:

  • orders cluster 提供 IOrderService、IInventoryService。
  • payments cluster 提供 IPaymentService。
  • 每个 cluster 内部都可能是单节点、静态多节点或 resolver 动态多节点。
  • 某些 Contract 程序集在编译时已知并静态注册。
  • 某些插件 Contract 程序集在运行时加载,并且必须显式注册到指定 cluster。

本 Issue 定义 0.7.10 的目标、限制、建议 API、内部实现路径、并发和生命周期语义、测试矩阵、验收条件、性能门禁及风险规避策略。实现者应严格按本文逐项核对;如果确实需要偏离,必须先在 PR 描述中解释原因、兼容性和性能影响。


总体结论

新增一个独立的多 Cluster 协调层,而不是把多个 Cluster 压平到现有单个 IEndpointClusterRuntime,也不允许在每次 RPC 调用时查找 cluster。

目标结构:

Application
    |
    | Get<TContract>():仅在创建 Proxy 时进行一次 Contract -> Cluster 路由
    v
SharpLinkMultiClusterClient
    |
    +-- orders   -> 一个现有 SharpLinkClient -> 单端点或 N 个 endpoint
    |
    +-- payments -> 一个现有 SharpLinkClient -> 单端点或 N 个 endpoint
    |
    +-- plugins  -> 一个现有 SharpLinkClient -> 单端点或 N 个 endpoint

每个 Cluster Slot 内部复用现有 SharpLinkClient。生成 Proxy 时,将目标 child client 或其动态模块 channel 直接传给 ProxyFactory。Proxy 创建后,RPC 热路径不得再次访问多 Cluster 路由表。


术语

为了避免与现有 UseCluster 混淆,本文使用以下术语:

  • Endpoint cluster:现有 0.7.5+ 的概念;一个 SharpLinkClient 内部的一组对等 endpoint。
  • Multi-cluster client:0.7.10 新增的协调器;拥有多个相互隔离的 SharpLinkClient。
  • Cluster Slot:Multi-cluster client 内部的一个命名成员;每个 Slot 对应一个现有 SharpLinkClient。
  • Contract route:ContractType/ContractId 到唯一 Cluster Slot 的映射。
  • Static contract assembly:构建 Multi-cluster client 前已经加载、具有 Source Generator Manifest,并由编译期 route manifest 指定 Cluster 的程序集。
  • Dynamic contract assembly:Build 后加载,并通过 RegisterAssembly(cluster, assembly) 注册到指定 Cluster 的程序集。
  • Routed assembly:拥有至少一个 Contract descriptor,并且已分配到唯一 Cluster 的程序集。
  • Dependency-only assembly:Manifest 不拥有 Contract,只提供 Codec 或其他生成依赖的程序集。

必须实现的目标

G1. 多 Cluster

一个 Multi-cluster client 必须能够配置至少一个 Cluster Slot。每个 Slot 必须可以使用现有 SharpClientBuilder 支持的任一拓扑:

  • fixed transport;
  • 单 endpoint;
  • 静态多 endpoint;
  • resolver 动态 endpoint。

Slot 内部的 Retry、Load Balancing、Admission、Circuit Breaker、连接池、认证、拦截器、序列化和协议配置继续使用现有 SharpClientBuilder 语义。

G2. Contract 唯一路由

每个已暴露 Contract 必须且只能映射到一个 Cluster Slot。

client.Get() 必须:

  1. 从原子 Contract route snapshot 中按 typeof(TContract) 查找 Cluster Slot;
  2. 只在 Get 阶段完成路由;
  3. 调用目标 child client 的 Get() 创建 Proxy;
  4. 返回直接绑定目标 IRpcChannel 的生成 Proxy。

调用 Proxy 方法之后,不允许:

  • 再按 Contract 查 Cluster;
  • 查 cluster 名称;
  • 解析字符串;
  • 创建路由上下文;
  • 给每个请求增加 cluster 字段;
  • 在 ClientConnection/PendingCall/请求帧中保存 cluster 名称。

G3. 编译期静态程序集路由

必须提供 Source Generator 可验证的静态程序集到 Cluster 映射。推荐 API:

[assembly: SharpLinkClusterContractAssembly(
    "orders",
    typeof(OrderContractsAssemblyMarker))]

[assembly: SharpLinkClusterContractAssembly(
    "payments",
    typeof(PaymentContractsAssemblyMarker))]

语义:

  • assemblyMarker 只用于定位其所在程序集。
  • 目标程序集 Manifest 中的全部 Contracts 作为一个不可拆分单元分配给指定 Cluster。
  • 同一 Contract-owning assembly 不得静态分配给多个 Cluster。
  • 同一映射重复声明到同一个 Cluster 可以去重,但 Generator 应给出不阻塞编译的提示或保持静默;不得生成重复 route。
  • 如果同一程序集或同一 Contract 被分配到不同 Cluster,必须产生编译期错误。
  • Attribute 应放在应用/Host 程序集,不能要求可复用 Contract 包把部署 cluster 名写死在包内。
  • Generator 必须输出独立的 generated cluster route manifest 和 ModuleInitializer 注册代码。
  • 不要修改现有 ISharpLinkGeneratedAssemblyManifest 的 API 版本;0.7.x 不应因为新增 Cluster 路由而要求所有旧 Contract 程序集重新生成。
  • 建议新增独立的 ISharpLinkGeneratedClusterRouteManifest 和弱引用 catalog。

必须支持 NativeAOT 的纯静态路径,不允许依赖反射扫描程序集内容。

G4. 指定 Cluster 的动态程序集注册

必须提供显式 Cluster 参数:

SharpLinkAssemblyRegistrationResult result =
    client.RegisterAssembly("payments", pluginAssembly);

SharpLinkAssemblyUnregisterResult drained =
    await client.UnregisterAssemblyAsync(
        "payments",
        pluginAssembly,
        TimeSpan.FromSeconds(10),
        cancellationToken);

SharpLinkAssemblyReplacementResult replaced =
    await client.ReplaceAssemblyAsync(
        "payments",
        oldAssembly,
        newAssembly,
        TimeSpan.FromSeconds(10),
        cancellationToken);

要求:

  • 复用现有 SharpLinkClient 的 Manifest 加载、依赖验证、冲突验证、模块租约、排空、定点取消和 ALC 释放机制。
  • 不得增加第二套 RPC Proxy/Codec/动态模块注册系统。
  • 注册必须是事务式的:失败后不能留下部分 route、部分 codec 或无法访问的 child registration。
  • Contract-owning dynamic assembly 只能路由到一个 Cluster。
  • Dependency-only assembly 可以注册到多个 Cluster,但必须以 (ClusterKey, Assembly object) 作为实例级所有权键,并且只有所有相关 Cluster 都注销后,外部 ALC 才可能被释放。
  • 动态 Contract assembly 的依赖必须已经注册在同一目标 Cluster;不得从另一个 Cluster 隐式借用 Codec 或模块。
  • NativeAOT 下继续返回现有 PlatformNotSupported 结构化结果;不得增加反射 fallback。

G5. 隔离性

Cluster 之间必须隔离:

  • endpoint selection;
  • Retry exclusion mask;
  • Circuit Breaker generation;
  • Admission policy;
  • Resolver 生命周期;
  • 连接池和连接状态;
  • PendingCall;
  • Streaming;
  • Health Check;
  • 认证与 transport factory 所有权。

一个 Cluster 的断连、Resolver 异常、Breaker Open、Retry 或动态拓扑替换不得将调用路由到另一个 Cluster,也不得修改其他 Cluster 的 Ready/Draining 状态。

G6. 兼容性和零禁用开销

不使用 Multi-cluster API 时:

  • SharpClientBuilder.Build() 必须继续构建现有 SharpLinkClient。
  • 现有 fixed endpoint fast path 不得新增分支、字段读取、字典查询、分配、锁或后台任务。
  • Protocol v2 wire format 和 handshake capability 不得变化。
  • 现有 ISharpLinkClient API 和行为不得发生破坏性变化。
  • 现有 0.7.9 Client/Server 必须保持互操作。

使用 Multi-cluster API 时:

  • 每次 Proxy 创建允许一次 route snapshot 查询。
  • Proxy 创建完成后的每次 RPC 调用必须与直接使用对应 child SharpLinkClient 的路径相同。
  • 不得在请求 Header、Metadata 或业务 Payload 中发送 cluster 名称。
  • 不得为了路由创建每调用 wrapper、delegate、closure 或 AsyncLocal。

明确的非目标

以下内容不属于 0.7.10;实现者不得顺便加入:

  • 同一 Contract 同时部署到多个 Cluster 后按调用选择 Cluster。
  • client.Get(cluster) 绕过唯一 Contract 路由。
  • 跨 Cluster failover。
  • 跨 Cluster Retry。
  • Cluster federation。
  • Contract 在运行中从一个 Cluster 无缝迁移到另一个 Cluster。
  • 在每个 RPC 帧中携带 cluster ID。
  • 新增协议版本或 handshake cluster fingerprint。
  • Server 在同一个 listener 上托管多个逻辑 Cluster。
  • 自动发现 Consul/Nacos/Etcd 中的“Cluster 列表”;现有 Resolver 仍只管理一个 Slot 内的 endpoint。
  • 动态拆分一个 Contract-owning assembly,使其中不同 Contract 路由到不同 Cluster。
  • 动态 Replace 时改变 ContractId 集合。
  • 懒连接、按首次 Get 自动启动 Slot;0.7.10 所有配置 Slot 都由 ConnectAsync 管理。
  • 修改现有 Retry、Breaker 或 endpoint selection 算法。

如果未来需要上述能力,应单独设计,不得为了“预留”而给当前每调用热路径增加间接层。


公共 API 参考

名称可以在实现前进行一次统一命名调整,但语义和边界必须保持。PR 中必须列出最终 API 对照。

Cluster Key

建议新增不可变值类型,内部和公共 API 不直接散布裸 string:

public readonly record struct SharpLinkClusterKey
{
    public SharpLinkClusterKey(string value);
    public string Value { get; }
    public override string ToString();
}

验证规则:

  • 非 null、非空。
  • 长度 1 到 64。
  • 第一个字符必须为 ASCII 字母或数字。
  • 后续字符仅允许 ASCII 字母、数字、点、下划线和短横线。
  • 使用 StringComparer.Ordinal,区分大小写。
  • 不得自动 Trim、转小写或进行 Unicode normalization。
  • 非法值抛 ArgumentException/ArgumentOutOfRangeException。
  • Generator 和 Runtime 必须使用完全相同的验证逻辑;验证逻辑应放在 Abstractions 可复用位置,避免两份实现漂移。

可以提供 string 到 SharpLinkClusterKey 的便利转换,但内部快照键必须是 SharpLinkClusterKey。

Builder

public sealed class SharpLinkMultiClusterClientBuilder
{
    public static SharpLinkMultiClusterClientBuilder Create();

    public SharpLinkMultiClusterClientBuilder Configure(
        Action<SharpLinkMultiClusterOptions> configure);

    public SharpLinkMultiClusterClientBuilder AddCluster(
        SharpLinkClusterKey cluster,
        Action<SharpClientBuilder> configure);

    public ISharpLinkMultiClusterClient Build();
}

参考用法:

var client = SharpLinkMultiClusterClientBuilder.Create()
    .Configure(options =>
    {
        options.MaxClusters = 16;
        options.MaxTotalConfiguredConnections = 64;
        options.MaxConcurrentClusterConnects = 4;
    })
    .AddCluster("orders", child => child
        .UseEndpoints(orderEndpoints, CreateOrderTransport)
        .UseCluster(options =>
        {
            options.MaxConnections = 8;
            options.MaxConnectionsPerEndpoint = 2;
        })
        .UseRetry())
    .AddCluster("payments", child => child
        .UseEndpointResolver(paymentResolver, CreatePaymentTransport)
        .UseCluster(options =>
        {
            options.MaxConnections = 4;
        })
        .UseCircuitBreaker(options =>
        {
            options.MinimumThroughput = 20;
        }))
    .Build();

await client.ConnectAsync();

IOrderService orders = client.Get<IOrderService>();
IPaymentService payments = client.Get<IPaymentService>();

AddCluster 的 configure 参数可以直接使用现有 SharpClientBuilder,但 Multi-cluster builder 必须通过 internal BuildCore/BuildContext 将过滤后的静态 Manifest 集合交给 child。不得先 Build 普通 child,再让每个 child 自动快照全部全局 Manifest。

Multi-cluster interface

建议:

public interface ISharpLinkMultiClusterClient : IAsyncDisposable
{
    SharpLinkMultiClusterState State { get; }

    ValueTask ConnectAsync(CancellationToken cancellationToken = default);
    ValueTask StopAsync(CancellationToken cancellationToken = default);

    TContract Get<TContract>() where TContract : IService;

    SharpLinkConnectionState GetClusterState(SharpLinkClusterKey cluster);

    ValueTask<SharpLinkHealthCheckResult> CheckHealthAsync(
        SharpLinkClusterKey cluster,
        CancellationToken cancellationToken = default);

    SharpLinkAssemblyRegistrationResult RegisterAssembly(
        SharpLinkClusterKey cluster,
        Assembly assembly);

    ValueTask<SharpLinkAssemblyUnregisterResult> UnregisterAssemblyAsync(
        SharpLinkClusterKey cluster,
        Assembly assembly,
        TimeSpan gracefulTimeout,
        CancellationToken cancellationToken = default);

    ValueTask<SharpLinkAssemblyReplacementResult> ReplaceAssemblyAsync(
        SharpLinkClusterKey cluster,
        Assembly oldAssembly,
        Assembly newAssembly,
        TimeSpan gracefulTimeout,
        CancellationToken cancellationToken = default);
}

ISharpLinkMultiClusterClient 不应继承 ISharpLinkClient,因为 ISharpLinkClient.CheckHealthAsync() 和 RegisterAssembly(Assembly) 在多 Cluster 下含义不明确。

Multi-cluster state

建议新增:

public enum SharpLinkMultiClusterState
{
    Created,
    Connecting,
    Ready,
    Degraded,
    Draining,
    Stopped,
    Faulted
}

最低语义要求:

  • Created:尚未开始共享 Connect。
  • Connecting:共享 Connect 已开始但尚未完成。
  • Ready:所有配置 Cluster 均至少达到各自现有 ConnectAsync 成功条件。
  • Degraded:Connect 曾成功,但之后至少一个 Cluster 不可用,同时至少一个 Cluster 仍可用。
  • Draining:Stop 已开始。
  • Stopped:所有 child、后台任务、resolver、transport factory 和动态模块清理流程均已完成。
  • Faulted:首次 Connect 失败并已完成部分启动清理,或出现不能继续安全使用的协调器级错误。

Cluster 自身的详细状态由 GetClusterState 返回。不得用协调器 State 替代 child 状态。

Options 和资源上限

建议:

public sealed class SharpLinkMultiClusterOptions
{
    public int MaxClusters { get; set; } = 16;
    public int MaxTotalConfiguredConnections { get; set; } = 64;
    public int MaxConcurrentClusterConnects { get; set; } = 4;
}

要求:

  • MaxClusters 硬上限不超过 256。
  • MaxConcurrentClusterConnects 范围 1 到 64。
  • Build 时对所有 child 的配置 MaxConnections 求和,并验证不超过 MaxTotalConfiguredConnections。
  • 如果某种 child 模式没有显式 MaxConnections,必须使用其最终冻结后的默认值参与计算。
  • AddCluster 不得覆盖同名 Cluster。
  • 没有任何 Cluster 时 Build 失败。
  • 静态 route 引用不存在 Cluster 时 Build 失败。
  • 允许一个 Slot 没有静态 Contract,但必须显式标记为允许动态 Contract;推荐在 AddCluster 增加 options 或单独的 AllowDynamicContracts 配置。默认空 Slot 构建失败,防止拼写错误造成空路由。

静态 route manifest 设计要求

建议新增以下独立模型,不修改现有 generated assembly manifest API:

public sealed record SharpLinkGeneratedClusterAssemblyRoute(
    SharpLinkClusterKey Cluster,
    Assembly ContractAssembly,
    string ContractAssemblyIdentity);

public interface ISharpLinkGeneratedClusterRouteManifest
{
    Assembly OwnerAssembly { get; }
    IReadOnlyList<SharpLinkGeneratedClusterAssemblyRoute> Routes { get; }
}

public static class SharpLinkGeneratedClusterRouteCatalog
{
    public static void Register(ISharpLinkGeneratedClusterRouteManifest manifest);
    public static IReadOnlyList<ISharpLinkGeneratedClusterRouteManifest> CreateSnapshot();
}

要求:

  • Catalog 使用弱引用和 collectible ALC Unloading 清理,行为参考现有 SharpLinkGeneratedAssemblyCatalog。
  • CreateSnapshot 返回强引用点时快照,但 Stop/Dispose 后协调器必须释放自己的强引用。
  • Route manifest 必须由 Host/Application 程序集拥有。
  • ContractAssemblyIdentity 必须用于结构化诊断;错误结果和异常文本不得长期强引用动态 Assembly/Type。
  • Generator 输出必须确定性:相同输入生成字节稳定、顺序稳定的 route descriptor。
  • Generator 必须验证 marker assembly 具有兼容的 SharpLink generated manifest。
  • 静态路由只包含被 route attribute 明确引用的程序集;进程中其他已加载但未声明的 Contract manifest 不应自动暴露,也不应导致 Multi-cluster Build 失败。
  • 一个 routed Contract assembly 的全部 Contracts、相关 generated Codec 和声明依赖必须放入同一个 child build manifest 集合。
  • 依赖 Manifest 若被多个 Slot 使用,可以在每个 child 的实例级 runtime context 中各自发布;不得引入进程级可变 Codec 状态。

内部数据结构建议

协调器至少应有:

private FrozenDictionary<Type, ClusterRouteRegistration> _routes;
private FrozenDictionary<SharpLinkClusterKey, ClusterSlot> _clusters;
private readonly Lock _registryGate;
private long _registryGeneration;

ClusterRouteRegistration 至少包含:

  • Contract Type。
  • ContractId。
  • Fingerprint。
  • 目标 ClusterSlot。
  • 静态或动态 Owner 信息。
  • 动态模块状态或足以定位 child registration 的信息。

ClusterSlot 至少包含:

  • SharpLinkClusterKey。
  • 内部 ISharpLinkClient/SharpLinkClient。
  • 冻结后的 child 配置摘要。
  • 是否允许动态 Contract。
  • 生命周期状态访问。
  • child 的静态 Manifest 集合。

关键规则:

  • _routes 的读取必须 Volatile.Read + FrozenDictionary.TryGetValue。
  • Get 不获取 writer lock。
  • 路由更新先构造完整候选快照,再 Volatile.Write 一次发布。
  • 不允许原地修改已发布 Dictionary、数组或 descriptor。
  • Cluster 集合 Build 后不动态增加/删除;0.7.10 只允许动态增加/删除指定 Slot 内的程序集路由。
  • 不要给每个调用创建 ClusterRouteRegistration 副本。

构建流程

Build 必须按以下顺序执行:

  1. 冻结并验证 Multi-cluster options。
  2. 冻结所有 AddCluster 配置,检查 ClusterKey 唯一性。
  3. 为每个 child 取得最终连接上限,检查全局预算。
  4. 从 SharpLinkGeneratedClusterRouteCatalog 取得静态 route manifest 快照。
  5. 展开 assembly marker 路由,并定位对应 ISharpLinkGeneratedAssemblyManifest。
  6. 验证目标 Cluster 存在。
  7. 验证同一 Contract-owning assembly 只路由到一个 Cluster。
  8. 验证跨所有 Slot 的 ContractType 和 ContractId 唯一。
  9. 验证 Fingerprint/Manifest API/Protocol 兼容性。
  10. 计算每个 Slot 所需的静态 Manifest 闭包,包括 Codec/依赖 Manifest。
  11. 检查依赖闭包没有缺失或跨 Slot 隐式借用。
  12. 通过 internal SharpClientBuilder.BuildCore(buildContext) 构建每个 child。
  13. child buildContext 只包含该 Slot 的静态 Manifest;普通 SharpClientBuilder.Build() 仍使用现有全量快照行为。
  14. 任一 child Build 失败时,以逆序释放之前已创建 child/transport factory;聚合清理异常,不得泄漏。
  15. 构造 Cluster 和 Contract FrozenDictionary。
  16. 返回协调器。Build 不连接网络。

必须避免直接修改 SharpLinkClient 的每调用入口。Manifest 过滤应通过构造/BuildContext 传入,而不是在 Invoke 时检查。


Connect、运行和 Stop 语义

Connect

  • ConnectAsync 是共享、幂等操作;并发调用等待同一个 Task。
  • 使用 MaxConcurrentClusterConnects 有界并发启动 child。
  • 0.7.10 所有配置 Slot 默认都是 required。
  • 只有所有 child ConnectAsync 成功,协调器 ConnectAsync 才成功并进入 Ready。
  • 任一 child 首次连接失败:
    • 停止继续调度尚未开始的 child;
    • 对已经成功或正在启动的 child 执行 StopAsync;
    • 等待全部清理;
    • 聚合保留原始连接错误和清理错误;
    • 协调器进入 Faulted;
    • 不留下可被 Get 后实际调用的部分启动对象。
  • 调用者 cancellation 只取消调用者等待还是取消共享首次 Connect,应与现有 SharpLinkClient.ConnectAsync 语义保持一致,并在文档中明确;不得出现一位调用者取消导致另一位调用者拿到半初始化状态。
  • 首次 Ready 后,某个 child 进入 Reconnecting/Faulted:
    • 协调器进入 Degraded;
    • 其他 child 保持可调用;
    • Contract 仍固定到原 Slot;
    • 不进行跨 Cluster fallback。
  • 所有 child 再次 Ready 后恢复 Ready。

Get

  • Created/Connecting 状态是否允许 Get 可以沿用现有 Client:允许创建 Proxy,但调用时由 child 的 readiness/WaitForReady 语义决定。
  • Draining/Stopped/Faulted 状态不得返回新动态模块 Proxy。
  • 未映射 Contract 抛 InvalidOperationException,消息包含 Contract full name,但不得泄漏 endpoint 地址。
  • 动态 route 为 Draining 时,Get 或后续调用必须得到稳定的 Unavailable/InvalidObjectState 语义,不能路由到别的 Slot。

Stop/Dispose

  • StopAsync 是共享、幂等操作。
  • 先原子进入 Draining,拒绝新的动态注册。
  • 对所有 child 发起 Stop;允许有界并行,但不能因为一个 child 抛异常就跳过其他 child。
  • 等待 child background task、resolver、connection、transport factory、动态 unregister cleanup。
  • 清空协调器强引用 route snapshot、cluster snapshot 和 route manifest snapshot。
  • 所有资源清理后进入 Stopped。
  • 一个异常直接重抛,多个异常 AggregateException。
  • DisposeAsync 调用 StopAsync。
  • 绝不在持有 coordinator writer lock 时 await child Stop/Unregister。

Lock 顺序

必须书面固定并在代码注释中记录:

  1. Coordinator registry/lifecycle gate。
  2. Child 自己的 registry/state gate。

禁止:

  • 持有 child gate 后回调 coordinator。
  • 持有 coordinator gate 时 await。
  • 在任何 gate 内调用用户提供的 resolver、selector、admission、retry、interceptor、authenticator、transport factory 或 ServiceProvider 代码。
  • 在 route snapshot 读路径加锁。

动态注册事务

RegisterAssembly(cluster, assembly) 至少按以下步骤:

  1. 参数和 ClusterKey 验证。
  2. 确认 Slot 存在且允许动态 Contract。
  3. 确认协调器不是 Draining/Stopped/Faulted。
  4. 使用现有 SharpLinkAssemblyManifestLoader 加载 Manifest。
  5. 计算 incoming ContractType/ContractId/Fingerprint。
  6. 在 coordinator writer gate 下,基于 _registryGeneration 和当前 route snapshot 检查:
    • 同 Assembly 是否已在目标 Slot 注册;
    • Contract-owning Assembly 是否已注册到其他 Slot;
    • ContractType/ContractId 是否与静态或动态 route 冲突;
    • 依赖是否在目标 child 注册;
    • 容量是否超限。
  7. 调用目标 child 的现有事务式 RegisterAssembly。
  8. child 失败时直接返回,coordinator route 不变化。
  9. child 成功后构造新的 Frozen route snapshot 并单次发布。
  10. 增加 generation,返回 Success。
  11. 如果 child 成功而 coordinator 发布前发生可恢复异常,必须立即对 child 启动零等待注销并返回结构化失败;PR 必须包含这个 rollback 的测试。
  12. OOM/StackOverflow 不需要伪装成结构化结果,但不得捕获后继续运行在未知状态。

为了降低复杂度,可以序列化 coordinator 动态 writer。Get 和 RPC 调用保持 lock-free。

并发竞态要求

以下竞态必须只有一个确定结果:

  • 两个线程将同一 Contract 注册到不同 Cluster:一个成功,一个稳定 Conflict;不能都成功。
  • Register 与 Stop:要么 Register 完整提交并由 Stop 清理,要么 Register 返回 InvalidObjectState;不能 orphan。
  • Register 与 Unregister 同一 (Cluster, Assembly):遵循现有 child 重入/共享 operation 语义。
  • Replace 与 Stop:publication 和 Draining 的先后必须线性化。
  • Get 与 route publication:只能看到旧完整快照或新完整快照。
  • Get 与 Unregister:已有 Proxy 按现有模块租约排空;新 Get 不得得到指向已 Released 模块的 Proxy。

Unregister 和 Replace 限制

Unregister

  • 参数必须包含 Cluster,且必须与实际 owner Slot 匹配。
  • 开始注销时 route 进入 Draining,不迁移到其他 Slot。
  • 已有调用和流继续使用现有 child 模块租约排空。
  • gracefulTimeout 后复用现有定点取消。
  • 调用者 cancellation 只停止等待,不回滚已开始的注销。
  • route 在注销完成/进入不可再接受新调用的状态后从 coordinator snapshot 删除。
  • ReferencesReleased=false 时不能重新暴露 route;后台释放完成后清理强引用。
  • 错误结果不得长期保存 Assembly/Type 强引用。

Replace

0.7.10 限制:

  • old 和 new 必须属于同一 Cluster。
  • new Manifest 的 ContractId 集合必须与 old 完全一致。
  • 不允许 Replace 借机新增、删除 Contract 或改变 Cluster。
  • Fingerprint/Schema 是否允许变化沿用现有 ReplaceAssemblyAsync 的兼容验证。
  • child publication 与 coordinator 所观察到的 route descriptor 更新必须作为一个逻辑事务。
  • 已取得的旧 Proxy 按现有模块 drain 语义结束;新的 Get 使用新 descriptor。
  • 若需要改变 Contract 集合,应先 Unregister,再 Register;不提供零间隙迁移保证。

Hosting 集成

必须新增与现有 Hosting 风格一致的入口,建议:

services.AddSharpLinkMultiClusterClient(builder =>
{
    builder
        .AddCluster("orders", child => child.UseTcp(...))
        .AddCluster("payments", child => child.UseEndpointResolver(...));
});

以及:

public interface ISharpLinkMultiClusterClientAccessor
{
    ValueTask<ISharpLinkMultiClusterClient> GetClientAsync(
        CancellationToken cancellationToken = default);
}

要求:

  • HostedService 在全部 required child 连接成功后发布 coordinator。
  • Start 失败时停止所有已启动 child,并让 accessor 等待者收到原始错误。
  • Stop 后 accessor 返回 unavailable。
  • 不向 DI 暴露内部 child ISharpLinkClient,避免调用方绕过 Contract route。
  • 同一个 IServiceCollection 默认只注册一个 multi-cluster coordinator;重复注册行为与现有 Hosting API 保持一致并有测试。
  • Anonymous Pipe 等只能用于单连接/特殊所有权的 transport,继续服从现有 child Builder 验证,不为 Multi-cluster 添加特殊绕过。

可观测性要求

必须做到:

  • 日志 scope 可以包含 cluster.name。
  • Activity 在 listener 启用时可以增加 sharplink.cluster.name。
  • 默认每调用 Metrics 不得增加 cluster.name、endpoint ID、地址、authority 或 transport 名称标签。
  • 可以增加低基数的 coordinator gauge/counter,例如 configured clusters、ready clusters、degraded transitions,但不得每个 Cluster 创建永久 Timer。
  • 不启用 Multi-cluster 时不得增加 listener 检查或 metric 调用。
  • 动态注册、冲突、启动失败和 Stop 清理错误日志必须包含 ClusterKey 和程序集字符串 identity。
  • 不得因日志/错误对象持有 collectible Assembly、Type、Manifest 或 ALC。

建议文件和实现阶段

阶段 1:Abstractions 与 Generator

建议新增/修改:

  • src/SharpLink.Abstractions/SharpLinkClusterKey.cs
  • src/SharpLink.Abstractions/ISharpLinkMultiClusterClient.cs
  • src/SharpLink.Abstractions/SharpLinkMultiClusterState.cs
  • src/SharpLink.Abstractions/SharpLinkGeneratedClusterRouteManifest.cs
  • src/SharpLink.Sdk/SharpLinkClusterContractAssemblyAttribute.cs
  • src/SharpLink.Generator/RpcGenerator.ClusterRouteAnalysis.cs
  • src/SharpLink.Generator/RpcGenerator.ClusterRouteEmitter.cs
  • AnalyzerReleases.Unshipped.md

先完成 Attribute、诊断、确定性生成和 catalog 测试。

阶段 2:Child Manifest 过滤

重构 SharpClientBuilder:

  • 保持 public Build() 行为不变。
  • 提取 internal BuildCore。
  • 增加 internal build context,可传入精确 static manifest snapshot。
  • SharpLinkClient 不再在字段初始化器中无条件抓取全局 Manifest;由构造参数决定,null 才代表普通现有行为。
  • 验证普通单 Client 的现有测试和性能完全不变。

这一阶段不得引入 Multi-cluster per-call 分支。

阶段 3:Coordinator 与静态路由

建议新增:

  • src/SharpLink.Client/SharpLinkMultiClusterClientBuilder.cs
  • src/SharpLink.Client/SharpLinkMultiClusterOptions.cs
  • src/SharpLink.Client/SharpLinkMultiClusterClient.cs
  • src/SharpLink.Client/SharpLinkMultiClusterClient.Lifecycle.cs
  • src/SharpLink.Client/SharpLinkMultiClusterClient.Assemblies.cs

完成 Build、Connect、Get、Health、Stop 和静态 route。

阶段 4:动态注册

实现 Register/Unregister/Replace、writer serialization、rollback、依赖验证和 ALC 清理。

阶段 5:Hosting、文档和性能

  • Hosting accessor/HostedService。
  • README 最小示例。
  • doc/architecture-0.7.10.md。
  • doc/migration-0.7.10.md。
  • doc/performance-0.7.10.md。
  • CHANGELOG 0.7.10。
  • PackageSmoke、NativeAOT、LoadTest/Benchmark 入口。

每个阶段都应保持可构建、测试通过;不要把全部变化堆在一个不可审查提交中。


必须完成的测试

下面是最低测试矩阵,不是可选建议。

A. Generator tests

  • 合法的单 route attribute 生成一个 route。
  • 同一 Host 程序集生成多个 Cluster route。
  • 同一 Contract assembly 重复映射到同一 Cluster 时确定性去重。
  • 同一 Contract assembly 映射到两个 Cluster 产生编译错误。
  • 两个 routed assembly 产生相同 ContractId 时产生编译错误或 Runtime 防御性失败。
  • marker Type 为 null/错误 typed constant 的诊断。
  • marker assembly 没有 SharpLink generated manifest 的诊断。
  • ClusterKey 空、过长、非法字符的诊断。
  • route 输出顺序与源文件/attribute 顺序无关,生成文本确定性。
  • nested/public Contract、引用程序集和多项目编译场景。
  • route manifest ModuleInitializer 只注册一次。
  • generated route catalog 不改变现有 Manifest API version。
  • incremental generator 缓存行为:无关语法变化不重新生成 Contract artifacts。
  • NativeAOT 所需成员有正确动态访问注解或完全不依赖反射。

B. Builder/unit tests

  • 零 Cluster Build 失败。
  • 重复 ClusterKey 失败。
  • 不存在的 route target Cluster 失败。
  • 空 Slot 默认失败。
  • 显式允许 dynamic-only 的空 Slot 成功。
  • MaxClusters、MaxTotalConfiguredConnections、MaxConcurrentClusterConnects 边界。
  • 单 endpoint child 仍构建 fixed fast path。
  • 多 endpoint child 构建现有 cluster runtime。
  • dynamic resolver child 构建现有 dynamic cluster runtime。
  • 每个 child 只收到属于自己的 static Manifest 闭包。
  • 未声明 route 的进程全局 Manifest 不会被任一 child 暴露。
  • routed dependency/codec 正确进入目标 child。
  • Build 中间失败时之前创建的 transport factory 恰好 Dispose 一次。
  • 多个 cleanup failure 被聚合。
  • 普通 SharpClientBuilder.Build() 仍快照现有全部静态 Manifest,行为不变。

C. Contract routing tests

  • Get() 返回绑定 orders child 的 Proxy。
  • Get() 返回绑定 payments child 的 Proxy。
  • 未映射 Contract 稳定失败。
  • route snapshot 发布前后的并发 Get 只能看到完整旧/新状态。
  • Proxy 创建后调用不再读取 coordinator route snapshot。
  • 可通过测试 hook/counter 证明一次 Get 后执行 100 万次 Unary,coordinator lookup counter 不增加。
  • 不缓存 Proxy 时行为与现有 Get 一致。
  • 动态模块 Proxy 仍使用 SharpLinkModuleRpcChannel/现有租约语义。
  • 一个 Cluster 断线时另一个 Cluster 的 Get 和 RPC 不受影响。
  • Retry 永远不会越过 Cluster。

D. Lifecycle integration tests

至少建立两个真实 Server:

  • 两个 Cluster 都是单节点。
  • 一个单节点、一个静态多节点。
  • 一个静态多节点、一个 resolver 动态节点。
  • 两个 Cluster 同时 Connect 成功。
  • 第二个 Cluster 首次 Connect 失败时,第一个已连接 Cluster被清理,coordinator Faulted。
  • Ready 后一个 Cluster 断线,coordinator Degraded,另一个继续 Unary/Streaming。
  • 故障 Cluster 恢复后 coordinator Ready。
  • 并发调用 ConnectAsync 共享同一操作。
  • Connect 与 Stop 竞态无泄漏。
  • Stop 多次和 Dispose 多次幂等。
  • 一个 child Stop 抛异常时其他 child 仍完成 Stop。
  • 所有 resolver、transport factory、connection、background task 最终归零。
  • CheckHealthAsync(cluster) 只检查指定 Slot。
  • Cancellation、Deadline、WaitForReady 在各 Slot 保持现有语义。
  • Unary、OneWay、client streaming、server streaming、duplex streaming 均路由正确。
  • 跨 Cluster 同时高并发不会串 request、stream 或 cancellation。

E. Dynamic assembly tests

  • RegisterAssembly 指定存在且允许动态的 Cluster 成功。
  • 注册到不存在 Cluster 返回稳定错误。
  • 注册到不允许动态的 Cluster 返回稳定错误。
  • 同一 Contract-owning Assembly 注册到第二个 Cluster 被拒绝。
  • 两个线程并发把相同 Contract 注册到两个 Cluster,恰好一个成功。
  • child registration 失败时 coordinator snapshot 不变。
  • coordinator publication 人工注入失败时 child registration 回滚。
  • 缺失依赖、错误 Cluster 中存在依赖仍必须失败。
  • dependency-only assembly 可以注册到两个 Cluster。
  • dependency-only assembly 从一个 Cluster 注销后另一个仍可用。
  • Unregister 排空现有 Unary 和所有 Streaming shape。
  • Unregister timeout 定点取消目标模块,不影响其他 Cluster。
  • caller cancellation 不回滚排空。
  • Register 与 Stop 竞态不产生 orphan registration。
  • Replace 只允许同 Cluster、相同 ContractId 集合。
  • Replace 改变 ContractId 集合被拒绝且旧 route 保持可用。
  • collectible ALC 在所有相关 route、Proxy、PendingCall、Stream、Codec 和错误结果释放后可被 GC。
  • ReferencesReleased=false 后 route 不重新开放,后台最终释放。
  • Stop 会清理所有动态模块,即使之前 Unregister 失败。
  • NativeAOT 返回 PlatformNotSupported,不扫描程序集。

F. Hosting tests

  • AddSharpLinkMultiClusterClient 注册 builder、HostedService 和 accessor。
  • 所有 Cluster Ready 后 accessor 才发布。
  • 任一首次启动失败,accessor 传播错误并清理已启动 child。
  • Stop 后 accessor 不再返回 client。
  • DI 不暴露内部 child ISharpLinkClient。
  • 重复注册和 logger factory 继承行为明确并测试。
  • Host 正常停止不产生日志噪音或未观察 Task 异常。

G. 协议和兼容测试

  • 0.7.10 普通单 Client 对 0.7.9 Server。
  • 0.7.10 Multi-cluster 中的 child 对 0.7.9 Server。
  • Request/Response/Stream 帧与 0.7.9 byte-for-byte 兼容。
  • handshake capability 不变化。
  • 数据包中不存在 ClusterKey。
  • 现有 PackageSmoke 全通过。
  • 现有 Linux/Windows/macOS Release Gate 全通过。
  • 纯静态 Multi-cluster NativeAOT smoke 通过。
  • 所有现有 Unit/Generator/Integration/Chaos 测试无回归。

H. Chaos/压力测试

  • Cluster A resolver 持续 add/remove/replace endpoint 时,Cluster B 稳定调用无错误。
  • Cluster A 持续注册/注销动态程序集时,Cluster B 的静态 Contract 持续调用。
  • Cluster A Breaker Open/HalfOpen 时 Cluster B 状态不变。
  • 同时 Stop、Resolver update、Register、Unregister、Streaming cancellation,不死锁。
  • 120 秒最小 Chaos:零 unexpected failure,结束后全部 gauge、活动调用、连接、stream、resolver worker 和模块引用归零。
  • 长时间测试至少覆盖 10 分钟混合 Unary/Streaming;正式 release gate 可继续要求更长时间。

验收条件

功能验收必须全部满足:

  • dev 分支版本更新为 0.7.10,CHANGELOG 完整。
  • 公共 API XML doc 完整,无公开类型/成员缺失文档。
  • 示例能够同时调用至少两个 Cluster 的不同 Contract。
  • 每个 Cluster 可独立选择单节点、静态多节点或动态 resolver。
  • 静态 Contract assembly 通过 Generator route manifest 绑定 Cluster。
  • 动态 Contract assembly 可注册到显式 Cluster。
  • Contract 跨 Cluster 冲突在 Generator 或 Build/Register 阶段稳定拒绝。
  • Get 时只路由一次;Proxy 调用不访问 coordinator。
  • 现有普通 SharpClientBuilder 和 ISharpLinkClient 行为不变。
  • Protocol v2 wire format 不变。
  • 首次部分启动失败完整清理。
  • Ready 后单 Cluster 故障不影响其他 Cluster。
  • Stop/Dispose/Unregister/Replace 在竞态下无泄漏、死锁和双重释放。
  • 动态 collectible ALC 有确定性释放测试。
  • Release build 0 warnings / 0 errors。
  • 全部 Unit、Generator、Integration、PackageSmoke、NativeAOT、Chaos 通过。
  • architecture、migration、performance 文档齐全。
  • PR 描述列出每个本 Issue checkbox 的对应测试或实现位置。

性能基准门禁

性能门禁必须以实现分支从 dev 分叉时的 0.7.9 commit 作为 baseline,并在报告中记录精确 SHA、机器、OS、SDK、CPU governor/电源状态和命令。禁止只给单轮最好结果。

测量方法

  • Release 构建,无 debugger/profiler。
  • baseline/candidate 交替 A/B,至少 5 轮。
  • 相同机器、相同进程模型、相同 concurrency、相同 payload。
  • 预热后采样。
  • 输出机器可读 JSON 和摘要。
  • 报告中给出每轮原始 QPS、P50、P99、错误数、CPU、进程分配及 BenchmarkDotNet B/op。
  • 以五轮中位数作为主要判定,同时检查是否存在持续方向性退化。
  • 所有性能测试必须零 unexpected failure。

Gate P1:未启用 Multi-cluster 的零回归

比较:

  • baseline 0.7.9 普通 SharpClientBuilder;
  • candidate 0.7.10 普通 SharpClientBuilder。

至少覆盖:

  • fixed TCP Unary 小 payload;
  • Unix Domain Socket;
  • Named Pipe(支持平台);
  • Shared Memory(支持平台);
  • c1、c8、c32、c128;
  • Rpc_Add BenchmarkDotNet 精确分配。

硬门禁:

  • Candidate QPS 中位数不得低于 baseline 的 99%。
  • Candidate P99 中位数不得高于 baseline 的 105%。
  • BenchmarkDotNet B/op 必须完全不增加;允许测量显示的自然对齐差异必须用反汇编/Allocation 证据解释并经人工接受,不能自行放宽。
  • 每调用进程级 allocation 不得增加。
  • fixed fast path 不得增加 coordinator 类型实例、后台 Task、Timer、route lookup 或 listener check。
  • 如出现超过门禁的退化,必须回退实现,不接受以“功能默认关闭”解释,因为目标是禁用时零热路径影响。

Gate P2:启用 Multi-cluster 后的调用路径

比较:

  • 直接使用一个配置完全相同的 child SharpLinkClient;
  • 通过 Multi-cluster client Get() 取得 Proxy 后调用同一 child。

测量阶段必须在 Proxy 已经取得后开始,排除一次性 Get 成本。

硬门禁:

  • QPS 中位数不得低于直接 child 的 99%。
  • P99 中位数不得高于直接 child 的 105%。
  • B/op 必须与直接 child 完全一致。
  • 采样/测试 hook 必须证明 RPC 次数增加时 coordinator route lookup 次数不增加。
  • 不得出现 per-call string/hash lookup、额外 delegate、wrapper allocation 或 AsyncLocal。

Gate P3:Cluster 隔离压力

场景:

  • Cluster A 持续 endpoint resolver 更新或 register/unregister。
  • Cluster B 持续调用静态 Unary。
  • 对照组为 Cluster B 单独运行相同调用。

门禁:

  • Cluster B QPS 中位数不得低于对照组的 97%。
  • Cluster B P99 中位数不得高于对照组的 105%。
  • Cluster A writer 活动不得让 Cluster B 调用进入 coordinator lock。
  • 零 unexpected failure。
  • 动态 writer 停止后稳态 allocation 回到基线。

Gate P4:启动和资源

这不是每调用热路径,但必须记录:

  • 2、8、16 个 Cluster 的 Build/Connect 时间。
  • configured connections 与实际连接数。
  • coordinator 额外托管内存。
  • Stop 总时间和资源归零时间。

门禁:

  • 连接数不得超过冻结配置总预算。
  • 每个 Cluster 不得新增除现有 child 所需之外的永久 Timer。
  • Stop 后所有 gauges 回零,resolver/transport factory 恰好释放一次。
  • 16 Cluster 的 coordinator 本身不得创建 O(cluster × contract × call) 的运行期结构;静态内存应近似 O(cluster + routed contracts)。

性能报告必须提交到 doc/performance-0.7.10.md;大型原始 artifacts 可以继续保存在忽略目录,但 PR/CI 必须保留足够的可复现命令和摘要。


可能风险与规避路线

R1. 每个 child 自动看到全部全局 Manifest

风险:Contract 边界失效、重复字典、Codec 错误共享、动态冲突。

规避:

  • 先实现 internal BuildContext 精确传入 static Manifest 闭包。
  • 普通 Build 传 null 保持旧行为。
  • Multi-cluster child 不允许自行调用全局 CreateSnapshot。
  • 增加测试直接检查 child route/codec snapshot。

R2. 在每次调用时路由 Cluster

风险:吞吐下降、分配增加、动态 remap 语义复杂。

规避:

  • 仅 Get() 查 route。
  • 复用 ProxyFactory(IRpcChannel)。
  • Proxy 直接捕获 child channel。
  • 禁止运行时跨 Cluster remap。
  • 用 lookup counter 和 BenchmarkDotNet 门禁。

R3. 动态模块/ALC 泄漏

风险:coordinator route、错误结果、日志、Proxy 或 Codec snapshot 强引用 collectible Assembly。

规避:

  • route 注销后原子替换 snapshot 并释放旧 snapshot。
  • 错误结果只保存字符串 identity。
  • 复用现有 module lease/drain。
  • dependency-only 多 Slot 注册使用明确 ref ownership。
  • Stop 清空全部强引用。
  • 强制 GC 的 collectible ALC integration test。

R4. 注册成功但 route 发布失败

风险:child 内存在不可访问模块。

规避:

  • writer serialization。
  • 发布前完整构造候选。
  • child 成功后 publication 异常执行零等待 rollback unregister。
  • fault injection test。
  • 不捕获 OOM/StackOverflow 后继续。

长期可考虑给 child registry 增加 internal prepare/commit 两阶段 API,但 0.7.10 不应为了它重写现有稳定注册器;只有当 rollback 无法满足线性化测试时才采用。

R5. Contract 重复或错误 Cluster

风险:Get 产生歧义,调用错误服务域。

规避:

  • Generator 编译期诊断。
  • Build/Register 防御性 ContractType + ContractId 双重校验。
  • 一个 Contract-owning assembly 一个 Cluster。
  • 不提供默认 Cluster 或 fallback。
  • 错误信息包含双方 cluster 和 assembly identity。

R6. 部分启动和 Host 泄漏

风险:Cluster A 已连接,Cluster B 失败,Host 启动失败但 A 仍运行。

规避:

  • 所有 Slot required。
  • 首次失败取消后续调度并 Stop 全部已启动 child。
  • HostedService 只在全部 Ready 后发布。
  • fault injection 覆盖 Connect/Handshake/Resolver/Dispose 失败。

R7. 锁顺序死锁

风险:coordinator register/stop 与 child unregister/stop 相互等待。

规避:

  • 固定 coordinator -> child 的同步 writer 顺序。
  • coordinator gate 内不 await。
  • Stop 先 snapshot child,再释放 gate,再 await。
  • child 不回调 coordinator。
  • Chaos 同时执行 Stop/Register/Resolver/Streaming cancel。

R8. Cluster 数量导致连接和后台任务爆炸

风险:每个 Slot 的 MaxConnections 合法,但总和不可控。

规避:

  • MaxClusters。
  • MaxTotalConfiguredConnections Build gate。
  • MaxConcurrentClusterConnects。
  • 文档说明每个 Slot 都拥有独立 heartbeat/resolver/connection worker。
  • 记录 2/8/16 Cluster 资源报告。

R9. Telemetry 标签基数

风险:cluster 名进入每调用 metric,长期内存/成本增加。

规避:

  • Metrics 默认不带 cluster.name。
  • Activity listener 开启时允许标签。
  • 日志 scope 带 cluster。
  • coordinator metric 只记录总量/状态转移。

R10. Retry 或 Breaker 穿越 Cluster

风险:业务隔离被破坏。

规避:

  • 每个 Slot 使用独立现有 SharpLinkClient。
  • 不把多个 Slot endpoint 压平。
  • Retry exclusion mask 只存在 child 内。
  • 集成测试让两个 Cluster 使用可区分响应,强制故障后验证不串流量。

R11. API 与现有 UseCluster 命名混淆

风险:用户误以为 AddCluster 和 UseCluster 是同一层级。

规避:

  • 文档固定 Endpoint cluster 与 Multi-cluster client 两个术语。
  • AddCluster 的 delegate 明确配置一个 child。
  • XML doc 说明 child.UseCluster 只配置 Slot 内资源。
  • 示例同时展示单 endpoint Slot 和 multi-endpoint Slot。

R12. NativeAOT 被动态需求拖入反射

风险:破坏现有静态 AOT 保证。

规避:

  • 静态 route 使用 generated manifest + ModuleInitializer。
  • 动态注册在 NativeAOT 明确 PlatformNotSupported。
  • 不扫描 AppDomain assemblies。
  • PackageSmoke 和独立 NativeAOT smoke 必须覆盖两个静态 Cluster。

R13. Replace 改变路由集合

风险:旧 Proxy、新 route 和 drain 状态难以线性化。

规避:

  • 0.7.10 只允许相同 ContractId 集合、同 Cluster Replace。
  • 改变集合必须 Unregister + Register。
  • 不承诺无间隙迁移。

R14. 同一个 dependency-only Assembly 多 Cluster 所有权

风险:一个 Slot 注销导致另一个 Slot Codec 失效或 ALC 过早释放。

规避:

  • ownership key 使用 (ClusterKey, Assembly object)。
  • 每个 child 拥有实例级 Codec snapshot。
  • coordinator 只在最后一个 owner 注销后释放自己的全局追踪。
  • 专门测试先后注销顺序。

文档要求

必须新增:

  • doc/architecture-0.7.10.md
    • 层级图;
    • route snapshot;
    • child ownership;
    • 注册/注销时序;
    • lifecycle state;
    • lock order;
    • NativeAOT。
  • doc/migration-0.7.10.md
    • 普通 Client 无需迁移;
    • 如何声明静态 route;
    • 如何注册动态程序集;
    • 0.7.10 限制。
  • doc/performance-0.7.10.md
    • baseline/candidate SHA;
    • 命令、环境、五轮表格;
    • P1-P4 结论。
  • README
    • 两 Cluster 最小示例。
  • CHANGELOG
    • 新增、兼容性、性能、限制。

公共 XML doc 必须明确“Cluster 选择发生在 Proxy 创建阶段,不在每次调用阶段”。


完成定义

只有同时满足以下条件,本 Issue 才能关闭:

  1. 所有功能和非目标边界已落实。
  2. 所有必须测试 checkbox 有对应自动化测试。
  3. 全量现有测试和跨平台门禁通过。
  4. NativeAOT 静态 Multi-cluster 通过。
  5. P1-P4 性能门禁通过并有报告。
  6. 无 Protocol v2 变化。
  7. 普通单 Client 热路径经代码审查确认无新增分支/查询/分配。
  8. 动态程序集注册、注销、Replace 和 ALC 释放通过竞态测试。
  9. 文档、示例、CHANGELOG 和 XML doc 完整。
  10. PR 描述提供“本 Issue 条目 -> 文件/测试/证据”的逐项映射。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions