Repository navigation
[Runtime Configuration][Client] Dynamic global retry policy #579
Description
Parent tracker: #262
Related retry foundation: #402
Related deadline/lifetime foundation: #287
Goal
Allow one running ISharpLinkClient instance to update its client-wide retry policy without rebuilding or replacing the Client.
This is a next-logical-call policy. A logical RPC captures one retry generation when it starts and keeps that generation across all of its attempts.
Runtime semantics
Support three client-wide retry modes:
Disabled
BuiltIn
Custom
Built-in mode keeps the existing bounded retry controls:
MaxAttempts
InitialBackoff
MaxBackoff
JitterRatio
Custom mode keeps framework ownership of attempts, deadlines, endpoint selection, pending calls and cancellation. ISharpLinkRetryPolicy only decides, after a failed eligible attempt:
ShouldRetry
Delay
Custom policy must remain bounded by a framework-owned MaxAttempts; it cannot create unbounded retries.
An update affects only logical calls that start after publication:
Call A captures retry generation N
runtime update publishes generation N+1
all attempts of A continue using generation N
Call B captures generation N+1
Existing retry eligibility remains unchanged
Runtime enablement does not make every call retryable:
[Idempotent] Unary -> eligible
non-idempotent Unary -> never retry
Streaming -> never retry
OneWay -> never retry
The existing built-in retryable error classification remains unchanged unless a separate issue explicitly changes it.
Relationship with request timeout
Retry and the client-wide request-timeout policy are independently configurable.
- Retry may be enabled while the client-wide timeout fallback is disabled.
MaxAttemptsstill bounds retry count when no logical-call deadline exists.- If a logical-call deadline exists from any existing source, all attempts and backoff share that one remaining budget.
DeadlineExceededterminates the logical call and is not itself a retryable attempt outcome under the current model.- Per-attempt timeout is out of scope.
Scope
- runtime disable of retry;
- runtime enable/update of built-in retry;
- runtime update of
MaxAttempts,InitialBackoff,MaxBackoff,JitterRatio; - runtime replacement with a custom
ISharpLinkRetryPolicyplus framework-owned attempt bound; - custom -> built-in / built-in -> custom / any -> disabled transitions;
- full candidate validation before atomic publication;
- one retry generation captured per logical call;
- all attempts of one logical call use the same captured generation;
- update vs Stop/Dispose race semantics;
- no new allocation/lock on retry-disabled healthy unary fast path;
- preserve existing endpoint-admission/circuit-breaker ownership; that subsystem remains separate.
Explicitly out of scope
- changing
[Idempotent]dynamically; - adding retry to Streaming or OneWay;
- changing the existing built-in retryable error set;
- per-attempt timeout;
- endpoint admission / circuit-breaker dynamic policy;
- topology/load-balancing dynamic configuration;
- caller-provided custom retry code executing RPC attempts itself;
- unbounded retry.
Correctness invariants
- A logical call captures one complete retry policy before its first attempt.
- A runtime update cannot change retry count/backoff/decision policy halfway through an existing logical call.
- Retry count remains bounded by
MaxAttempts, including the first attempt. - Existing deadline/cancellation/Client Stop semantics remain authoritative and can terminate retry early.
- Invalid built-in options or invalid custom policy publication leave the current generation unchanged.
- Custom policy failures fail only the affected logical call according to the existing failure contract; they do not corrupt Client state.
- No second pending table, attempt registry, or endpoint state machine is introduced.
Validation
At minimum cover:
- disabled -> built-in;
- built-in parameter update;
- built-in -> disabled;
- built-in -> custom;
- custom -> built-in/custom replacement;
- invalid option rollback;
- update while a retrying call is between attempts;
- old call uses old generation while a new call uses the new generation;
- retry enabled with global request timeout disabled;
- retry with an existing logical-call deadline;
- caller cancellation / Stop during backoff;
- concurrent repeated updates;
- retry-disabled healthy unary allocation/performance gate.
Completion
This issue is complete when a running Client can atomically replace its global retry behavior for future logical calls while every in-flight logical call remains bound to the retry generation it captured at start.
Activity
SunSi12138 commentedon Sep 7, 2026
Review pass for #594: no engineering-value issues found. After #593 merged first, #594 had a mechanical overlap in
ISharpLinkClient.csandSharpLinkClient.CallOptions.cs; the branch now incorporatesdev@d03ef7ed0ab3f9171178f8fde0c2b36ba1b6efd5while preserving both runtime timeout and runtime retry generation semantics. The PR is mergeable again and its final diff remains limited to the 9 retry-related files. Fresh PR Fast/CodeQL checks are running on the resolved headd304cdf0be3cf1a618bad50eea68f04aa5a04779. Repository auto-merge is disabled, so this issue remains open until the gated merge completes.SunSi12138 commentedon Sep 7, 2026
PR #594 has passed the required CI checks and was squash-merged into
dev.Merge commit:
fd8832ce7c4b66dee21b2e14ffb7400f4ebab21aRuntime retry-policy generation updates are now integrated; closing this issue as completed.