Skip to content

[Runtime Configuration][Client] Dynamic endpoint selection and load-balancing policy #591

Closed

Description

@SunSi12138

Parent tracker: #262

Analysis / implementation baseline: dev.

Goal

Allow a running multi-endpoint ISharpLinkClient to replace the endpoint-selection policy used for future attempts without rebuilding the Client, reconnecting healthy sessions, or changing the current endpoint topology.

This issue covers both existing built-in strategies and the existing custom selector SPI:

Built-in:
  PowerOfTwoChoices
  Random
  RoundRobin
  LeastPending

Custom:
  ISharpLinkEndpointSelector

The intended capability is dynamic policy replacement, not runtime registration of new enum values.

Examples:

P2C -> LeastPending
RoundRobin -> custom ISharpLinkEndpointSelector
custom selector A -> custom selector B
custom selector -> P2C

Runtime semantics

Endpoint selection is a next-attempt policy.

selection policy generation N
        ↓
Attempt A selects endpoint using N
        ↓
control path publishes N+1
        ↓
Attempt A keeps the endpoint it already selected
future attempt B selects using N+1

Do not reselect an already-started attempt merely because configuration changes.

For a retrying logical call, each new physical attempt may observe the latest selection policy unless the existing retry/attempt architecture requires a stronger capture boundary. The implementation must document the chosen boundary and keep one individual attempt internally consistent.

Scope

  • runtime replacement among all existing built-in load-balancing strategies;
  • runtime replacement built-in -> custom ISharpLinkEndpointSelector;
  • runtime replacement custom -> built-in;
  • runtime replacement custom selector A -> B;
  • complete candidate validation before atomic publication;
  • static endpoint-cluster support;
  • dynamic-resolver endpoint-cluster support;
  • future endpoint attempts use the new policy without reconnecting healthy sessions;
  • current resolver/topology snapshot remains authoritative and independent;
  • update vs topology replacement / endpoint retirement / Stop / Dispose races;
  • no new request-path lock or per-attempt allocation solely for dynamic configuration;
  • benchmark P2C/Random/RoundRobin/LeastPending and custom-selector steady state before/after dynamic support.

Fixed/single-endpoint topology has no meaningful load-balancing choice and should remain on its specialized fast path.

Important boundary: policy replacement != topology mutation

This issue changes only:

"given the current Ready endpoint snapshot, which endpoint should this attempt choose?"

It does not change:

  • endpoint membership;
  • resolver snapshots;
  • endpoint address/authority;
  • connection-pool capacity;
  • endpoint readiness;
  • retry eligibility;
  • endpoint admission / Circuit Breaker state.

Dynamic resolver updates remain owned by the resolver/topology lifecycle. Connection-pool scaling remains tracked separately.

Custom selector semantics

ISharpLinkEndpointSelector is already the extension point for application-defined synchronous endpoint selection. Runtime replacement should reuse that SPI rather than adding a second dynamic-selector abstraction.

Requirements:

  • one selection operation invokes exactly one captured selector/policy;
  • a policy update cannot cause one selection to call two selectors or combine outputs;
  • replaced selector state is not implicitly migrated into the new selector;
  • framework ownership/disposal of custom selector objects follows the existing SPI ownership contract; do not invent disposal semantics solely for hot reload;
  • selector callbacks remain synchronous/non-blocking according to the existing contract;
  • invalid selector output remains rejected exactly as today.

Built-in strategy state

Built-in strategies may own small local state such as RoundRobin / LeastPending cursors.

The implementation must explicitly decide whether compatible state is preserved across updates. Correctness does not require historical cursor continuity, but updates must not create unbounded state, cross-topology stale indexes, or races with endpoint snapshot replacement.

Prefer stable runtime state separated from the immutable current selection policy when that keeps the hot path simpler.

Publication model

Conceptually:

immutable EndpointSelectionPolicy N
        ↓
atomic publish
        ↓
new attempt reads N once
        ↓
selects from one Ready endpoint snapshot

update

immutable EndpointSelectionPolicy N+1

The selection policy and endpoint topology snapshot are separate publications. One attempt must use a valid combination: the selector can only return an endpoint/index from the Ready snapshot supplied to that selection operation.

Do not introduce a global configuration lock around endpoint selection.

Correctness invariants

  1. One endpoint selection operation uses one policy generation.
  2. An already-selected/in-flight attempt is never moved to another endpoint because policy changed.
  3. The selected endpoint must belong to the Ready snapshot used for that selection.
  4. Resolver/topology replacement cannot make a stale selector result newly eligible.
  5. Retry exclusion / endpoint-admission / Circuit Breaker rules remain authoritative after policy selection.
  6. Custom selector replacement cannot leak or retain obsolete topology generations.
  7. Stop/Dispose seals further selection-policy publication.
  8. Fixed/single-endpoint fast path remains unchanged.
  9. No per-attempt allocation, global lock, or unnecessary virtual dispatch is added when using built-in strategies beyond the existing selection cost.

Validation

At minimum cover:

  • P2C -> Random -> RoundRobin -> LeastPending transitions;
  • built-in -> custom;
  • custom -> built-in;
  • custom A -> custom B;
  • update while many concurrent selections run;
  • update while resolver publishes a new topology;
  • update while endpoint is retiring/draining;
  • update while retry is choosing a later attempt;
  • update with endpoint admission / Circuit Breaker enabled;
  • invalid custom selector result after update;
  • update vs Stop/Dispose;
  • repeated updates do not retain obsolete selector/policy/topology state;
  • static and dynamic endpoint clusters;
  • fixed topology remains allocation/performance-identical.

Performance gates

Endpoint selection is a hot path. Compare same-runner base/head for at least:

P2C
Random
RoundRobin
LeastPending
custom selector
steady state after repeated updates

at representative concurrency.

Record:

  • ns/op or selection throughput;
  • end-to-end RPC QPS;
  • P50/P99 where practical;
  • B/op / objects-op;
  • contention.

Requirements:

  • no request-path global lock;
  • no per-attempt configuration object allocation;
  • built-in steady-state performance should remain within normal benchmark noise;
  • fixed endpoint fast path must not gain a dynamic-selection branch.

Explicitly out of scope

  • runtime registration of new SharpLinkLoadBalancingStrategy enum values;
  • endpoint add/remove/address changes;
  • resolver replacement;
  • connection-pool scaling;
  • retry-policy changes;
  • endpoint admission / Circuit Breaker policy updates;
  • cross-cluster routing/failover;
  • transport/TLS/authentication replacement;
  • per-call user override of load-balancing strategy;
  • a giant unified runtime options object.

Completion

Complete when a running multi-endpoint Client can atomically replace its built-in or custom endpoint-selection policy and future attempts use the new policy without reconnect, topology mutation, attempt inconsistency, or hot-path regression.

Activity

  1. SunSi12138 commented on Sep 8, 2026

    @SunSi12138
    OwnerAuthor

    Implemented and self-reviewed in Draft PR #606 (feat(client): update endpoint selection policy at runtime).

    Current clean head: 7e545b185b115ba313fba84460e0addc9362853a, based directly on dev@76b0744941155912582483714466e07cbb50025e, one commit ahead / zero behind.

    Self-review found no production correctness blocker, but it did identify missing acceptance evidence in the first candidate. The PR now directly covers:

    • live static Ready-client routing replacement with 64 concurrent future RPCs and no reconnect;
    • live dynamic Ready-client routing replacement concurrent with resolver topology publication;
    • endpoint retirement/draining with an accepted stream remaining bound to the retiring endpoint;
    • later retry physical attempts observing the latest policy generation;
    • admission/local reselection preserving one captured generation within a physical attempt;
    • invalid runtime selector output, Stop/Dispose publication sealing, and concurrent writer/reader behavior.

    Validation/evidence:

    • full self-review Integration suite: 451/451;
    • strict steady-state selection gate: 0 B over 100,000 attempts after repeated publication;
    • same-runner 32-concurrent RPC performance evidence, including 1,000 live policy publications after the client was already Ready, showed no generation-retention/allocation-growth trend;
    • fixed/single-endpoint A/B remained 808 B/op with essentially unchanged latency;
    • exact-head PR Fast, CodeQL, Pending/codec, and Extension fault-containment CI are green.

    The PR remains Draft for maintainer review.

  2. SunSi12138 commented on Sep 8, 2026

    @SunSi12138
    OwnerAuthor

    Implemented and reviewed in #606, now squash-merged into dev as 92b56d87593c26022b25dc29c21ed31a3d5d9b30.

    The Client now exposes one runtime-replaceable endpoint-selection policy for multi-endpoint static and dynamic-resolver clusters. Each physical attempt captures one immutable policy generation and reuses it through admission/exclusion reselection, while later physical retry attempts may observe a newer generation. Ready-topology publication remains independent, RoundRobin/LeastPending cursor state remains topology-owned, healthy sessions are not reconnected by policy publication, and Stop/Dispose seals further updates through the existing lifecycle gate. Fixed/single-endpoint clients remain on the specialized fast path and reject this multi-endpoint runtime API.

    Review found no production correctness/lifecycle blocker. Coverage includes all built-in/custom transitions, live static/dynamic updates, concurrent topology replacement and policy publication, retirement/draining of accepted work, retry-generation boundaries, admission-local reselection consistency, invalid candidate/output behavior, Dispose sealing, and strict zero-allocation selection evidence. Same-runner concurrent performance evidence showed no generation-retention/allocation growth after 1,000 live publications; the fixed path remained allocation-identical.

    Exact head 7e545b185b115ba313fba84460e0addc9362853a passed CodeQL, pending/codec, extension fault containment, full integration evidence, same-runner performance evidence, final PR Extended #158, and the required PR Fast check after unrelated intermittent test failures were separately recorded in #387. Closing as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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