feat: Hand over the data store to the new client on re-anchor#736
Merged
Conversation
SSERelayDataStoreAdapter.Build reuses its existing wrapper when one is already present, so a concurrent-keys re-anchor hands the populated, initialized store to the new anchor's client instead of building a fresh, empty one -- no empty-store window, no re-sync. The wrapper is refcounted: acquire() bumps the holder count on each Build reuse and Close() decrements, tearing down the underlying store only on the final release. This keeps a retiring client's Close() from pulling the store out from under the new anchor. A fully-closed wrapper refuses re-acquisition so Build rebuilds rather than resurrecting a dead store. Inverts the H1/H5 re-anchor PoC tests from asserting the pre-fix (store wiped) behavior to asserting handover, and adds a real-client spike test and a refcount-contract regression test. H6/H7 remain as-is pending the synchronous re-anchor change.
This was referenced Jul 6, 2026
keelerm84
added a commit
that referenced
this pull request
Jul 6, 2026
A stray or duplicate Close on a fully-released streamUpdatesStoreWrapper drove refCount negative and re-closed the underlying store (double-releasing a persistent store's connection pool). Close now short-circuits once closed. Adds a refMu-guarded currentRefCount() accessor and concurrency tests: idempotency, an 8-way concurrent double-close, and a Build-reuse/Close race (all -race clean). Found by multi-agent review of #736.
A stray or duplicate Close on a fully-released streamUpdatesStoreWrapper drove refCount negative and re-closed the underlying store (double-releasing a persistent store's connection pool). Close now short-circuits once closed. Adds a refMu-guarded currentRefCount() accessor and concurrency tests: idempotency, an 8-way concurrent double-close, and a Build-reuse/Close race (all -race clean). Found by multi-agent review of #736.
5938a18 to
e598fa9
Compare
kinyoklion
approved these changes
Jul 6, 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
First slice carved out of #720 (the re-anchor mechanism, SDK-2542), split for reviewability. This is the store-handover piece and is independent of the credential and relayenv changes -- it adds a latent capability that the synchronous re-anchor will use in a later PR.
SSERelayDataStoreAdapter.Buildnow reuses its existing wrapper when one is already present, so a concurrent-keys re-anchor can hand the populated, initialized store to the new anchor's client instead of building a fresh, empty one -- no empty-store window, no re-sync. The wrapper is refcounted:acquire()bumps the holder count on eachBuildreuse andClose()decrements, tearing down the underlying store only on the final release. This keeps a retiring client'sClose()from pulling the store out from under the new anchor. A fully-closed wrapper refuses re-acquisition, soBuildrebuilds rather than resurrecting a dead store.Tests
TestRealClient_HandoverPreservesUnderlyingStore-- a real-client spike verifyingld.LDClient.Close()does not tear down the shared underlying store.store_rebuild_after_close_test.go-- a refcount-contract regression covering the "fully-closed wrapper refuses re-acquisition" path.Split sequence
This is PR 1 of a 4-slice split of #720: store handover (this PR) -> relayenv refactors -> credential rotator deferred-flip API -> synchronous re-anchor core.
Note
Medium Risk
Changes core data-store lifecycle on SDK client swap (refcounted Close and Build reuse); impact is limited to the store adapter layer in this slice, with broad regression and race tests, but mistakes could cause use-after-close or double-close on persistent stores.
Overview
Adds store handover so a concurrent-keys re-anchor can stand up a new upstream
LDClientwithout wiping the in-memory feature store.SSERelayDataStoreAdapter.Buildnow returns the existingstreamUpdatesStoreWrapperwhen one is already parked, usingacquire()to bump a holder refcount instead of building a fresh empty wrapper.Close()on the wrapper only tears down the underlying store on the final release, so a retiring client’s shutdown does not close the store while the new anchor still holds it. Fully closed wrappers refuse re-acquisition so a laterBuildcreates a new wrapper rather than resurrecting a dead store.Re-anchor PoC tests H1/H5 are flipped to expect handover (same store instance, data preserved through
env_context). New coverage includes real-LDClienthandover, rebuild-after-full-close, and refcount idempotency / handover-vs-Close race tests (intended for-race).Reviewed by Cursor Bugbot for commit e598fa9. Bugbot is set up for automated code reviews on this repo. Configure here.