feat: Defer the SDK anchor flip to an explicit CommitAnchor#738
Merged
keelerm84 merged 1 commit intoJul 8, 2026
Merged
Conversation
Reconcile no longer flips the rotator's SDK anchor pointer when the anchor changes. It returns a ReconcileResult reporting the AnchorChange, and a new CommitAnchor method moves the pointer -- the seam the synchronous re-anchor needs to build the new anchor's client before the pointer moves. Because the flip is now a second lock acquisition (CommitAnchor) rather than done in place inside Reconcile, it must stay atomic with the addition Reconcile queues. A new reconcileMu serializes reconcileCredentials against the cleanup ticker's triggerCredentialChanges, so the ticker cannot drain the new anchor's queued addition in the window before CommitAnchor -- which would otherwise run addCredential with the anchor still on the old key, skip startSDKClient, and leave the env with no upstream client. reconcileCredentials commits and drains under reconcileMu; the drain body is extracted to drainCredentialChanges (lock-free, caller holds reconcileMu) to avoid re-entrancy. Behavior-preserving, including under concurrency. The additions-stripping, rollback, and mobile-primary repoint that build on this seam land with the synchronous re-anchor itself. Tests: rotator unit tests for the deferred-flip contract; a serialization regression asserting the ticker blocks while a reconcile holds reconcileMu.
0c4bb5a to
6f7b4fb
Compare
kinyoklion
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third slice carved out of #720 (the re-anchor mechanism, SDK-2542), split for reviewability. Introduces the deferred anchor-flip seam the synchronous re-anchor is built on. Sibling of #736 (store handover, merged) and #737 (relayenv refactors) off
feat/concurrent-keys.Rotator.Reconcileno longer flips the SDK anchor pointer when the anchor changes. It returns aReconcileResultreporting theAnchorChange(previous -> new), and a newCommitAnchormethod moves the pointer. This is the seam the re-anchor needs: it lets the caller build (and initialize) the new anchor's SDK client before the pointer moves, instead of flipping first and racing an async build.Serialization (why this is safe under concurrency)
Deferring the flip means it is now a second lock acquisition (
CommitAnchor) rather than done in place insideReconcile. That would open a window whereReconcilehas queued the new anchor's addition but the pointer has not moved yet -- and the per-env cleanup ticker (triggerCredentialChanges) could drain that addition, runaddCredentialwith the anchor still on the old key, skipstartSDKClient, and leave the env with no upstream client. To close it, a newreconcileMuserializesreconcileCredentialsagainst the cleanup ticker: the Reconcile -> CommitAnchor -> drain sequence is atomic against the ticker, exactly as concurrent reconciles already were.reconcileCredentialsdrains via a lock-freedrainCredentialChanges(nottriggerCredentialChanges) to avoid re-enteringreconcileMu.Behavior-preserving, including under concurrency: by the time
addCredentialruns for the new anchor,AnchorKey()already names it, so its upstream client starts exactly as before. Verified withgo test -race,go vet, andmake lint.Tests
Reconcilereports the change but leaves the pointer untilCommitAnchor, and signalsnilwhen unchanged.triggerCredentialChangesblocks while a reconcile holdsreconcileMu, then proceeds once released.Deliberately out of scope (they land with the synchronous re-anchor, #739)
Stripping the new anchor from
additions, rollback (RevertAnchorChange), and the mobile-primary repoint -- each depends on the synchronous client build to be correct, so they are not in this PR.Split sequence
PR 3 of 4: store handover (#736, merged) -> relayenv refactors (#737) -> credential deferred-flip (this PR) -> synchronous re-anchor core (#739).
Note
Medium Risk
Touches credential rotation and per-env upstream client startup under concurrency; behavior is intended to stay equivalent when
CommitAnchorruns before drain, but the new mutex and API contract are on a critical path for SDK key re-anchoring.Overview
Introduces a deferred SDK anchor flip so callers can move the upstream anchor only when ready (e.g. after building the new client), instead of updating the pointer inside
Rotator.Reconcile.Credential rotator:
Reconcilenow returnsReconcileResultwith an optionalAnchorChange(previous → new anchor) and no longer assignsanchorKeyinreconcileSDKKeys. Callers apply the transition with newCommitAnchor. Accepted-set diffs and queued additions/expirations behave as before.Relay environment:
reconcileCredentialsrunsReconcile→CommitAnchor(when needed) →drainCredentialChangesunder newreconcileMu, so that sequence cannot interleave with the cleanup ticker’striggerCredentialChanges. Draining is split intodrainCredentialChangesto avoid re-entering the mutex from reconcile. ImmediateCommitAnchorbefore drain preserves today’s behavior: whenaddCredentialruns for the new anchor,AnchorKey()already matches it.Tests: Rotator tests cover deferred flip, nil
AnchorChangewhen unchanged, and updated flows that callCommitAnchor. A relayenv regression asserts the ticker blocks onreconcileMuwhile reconcile holds it.Reviewed by Cursor Bugbot for commit 6f7b4fb. Bugbot is set up for automated code reviews on this repo. Configure here.