Repository navigation
Description
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
- One endpoint selection operation uses one policy generation.
- An already-selected/in-flight attempt is never moved to another endpoint because policy changed.
- The selected endpoint must belong to the Ready snapshot used for that selection.
- Resolver/topology replacement cannot make a stale selector result newly eligible.
- Retry exclusion / endpoint-admission / Circuit Breaker rules remain authoritative after policy selection.
- Custom selector replacement cannot leak or retain obsolete topology generations.
- Stop/Dispose seals further selection-policy publication.
- Fixed/single-endpoint fast path remains unchanged.
- 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
SharpLinkLoadBalancingStrategyenum 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
SunSi12138 commentedon Sep 8, 2026
Implemented and self-reviewed in Draft PR #606 (
feat(client): update endpoint selection policy at runtime).Current clean head:
7e545b185b115ba313fba84460e0addc9362853a, based directly ondev@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:
Validation/evidence:
The PR remains Draft for maintainer review.
SunSi12138 commentedon Sep 8, 2026
Implemented and reviewed in #606, now squash-merged into
devas92b56d87593c26022b25dc29c21ed31a3d5d9b30.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
7e545b185b115ba313fba84460e0addc9362853apassed 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.