Repository navigation
Serve replicator-mode flag checks only once the cache is ready - #204
Open
ryanechternacht wants to merge 1 commit into
Open
ryanechternacht wants to merge 1 commit into
ryanechternacht wants to merge 1 commit into
Conversation
In replicator mode, flag checks now read the replicator cache only after the replicator reports ready (HTTP 200 with ready: true). Before that, CheckFlag, CheckFlagWithEntitlement and CheckFlags skip the cache and use the API. Once ready, both evaluate from the cache like the Go SDK, and a flag missing from the cache falls back to the API. Single and bulk flag checks share one gate, Schematic.UseDatastreamCache(), which requires datastream and IsCacheReady(). DatastreamClientAdapter.CheckFlag also refuses to read the cache while it is not ready. Add IsCacheReady() to DatastreamClientAdapter and Schematic. It reports replicator readiness in replicator mode and is true in websocket mode, matching Go. The health poll now records cache_version only when a response carries a non-empty one, and treats a request timeout as a failed poll. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2 of 3 tasks
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.
Depends on SchematicHQ/schematic-replicator#143. Do not merge before it. That PR changes
/readysoready: truemeans the cache is complete for itscache_version, which is what this gate relies on.Problem
In replicator mode, single and bulk flag checks both went through
DatastreamClientAdapter.CheckFlag, and it read the replicator cache before looking at readiness. What happened next depended on whether the replicator was ready and what the cache held:FlagNotFoundwithfalse, with no API fallback. Go falls back to the API here.So a check made while the replicator was still loading could give a wrong verdict from a partial cache. Whether it did depended on which entities happened to be written yet.
Change
DatastreamClientAdapter.IsCacheReady()andSchematic.IsCacheReady()are new. In replicator mode they return the replicator readiness from the health poll. Outside replicator mode the adapter returns true, matching Go, because websocket mode fills its own cache and fetches misses on demand.Schematic.IsCacheReady()returns false when datastream is not in use.Schematic.UseDatastreamCache()is one private helper,_datastreamClient != null && _datastreamClient.IsCacheReady(). It replaces the_datastreamClient != nullcheck in bothCheckFlagWithEntitlement(whichCheckFlagcalls) andCheckFlagswith keys. If the cache is not ready, both skip the cache and take the existing API path, which falls back to flag defaults if the API fails.DatastreamClientAdapter.CheckFlagalso throws before reading the cache whenIsCacheReady()is false, so a direct caller of the public adapter can't read an unready cache either. When ready, it evaluates from the cache like Go: a flag missing from the cache throws so the caller falls back to the API (previously it returnedFlagNotFound), and a missing company or user evaluates with what the cache holds.IsReplicatorReady()/IsReplicatorHealthy()keep their behavior. Their doc comments now say they report replicator readiness in replicator mode and point toIsCacheReady.IsConnectedAsyncreports websocket state and was never the replicator readiness signal in this SDK (it stays false in replicator mode). Its doc comment now says so and points toIsCacheReady.Health poll (
ReplicatorHealthService): it already parsed the body on any status, including a 503. Two fixes:cache_versiononly when the response has a non-empty one. Before, a response without one set the version to null.HttpClientraises asTaskCanceledException) is now handled as a failed poll: not ready, last version kept. Before, it was rethrown as if it were shutdown and logged at Error level by the loop.Readiness still requires HTTP 200 and
ready: true, the existing behavior. Go reads only thereadyfield. The two differ only for a 503 withready: true, which the replicator does not send.README replicator section: the SDK serves from the cache only once the replicator reports ready. Before that, flag checks use the API.
Websocket mode is unchanged.
Track's local metric update is not touched.Parity with Go
This matches SchematicHQ/schematic-go#240: an
IsCacheReadythat returns true outside replicator mode, and one shared gate (useDataStreamCache()in Go,UseDatastreamCache()here) at the single and bulk flag-check branches. One difference remains. C# bulkCheckFlagsstill evaluates each key through the same adapter method rather than in one rules-engine call. Both fall back to the API for the whole set if any key fails, so the results match.There is no credit-lease prewarm or other non-flag-check cache read on main in this SDK. The only other datastream cache write is
Track's metric update, which is out of scope.Overlap with open PRs
Trackblock inSchematic.csand removesUseDatastream(). This PR does not touch theTrackblock, and the new gate does not callUseDatastream(), so the two merge cleanly.UseDatastream(). That conflict does not come from this PR. With that conflict resolved, all three merged together build and pass the full suite (472 tests).Tests
New
src/SchematicHQ.Client.Test/Datastream/ReplicatorCacheReadyTests.cs. It coversDatastream/, which is already in.fernignore. The tests seed the cache under the versioned flag key and the company key lookups the replicator writes. They use a controllable health service and a fake API that counts single and bulk requests:CheckFlag,CheckFlagWithEntitlementandCheckFlagsall use the API and return its values, and the replicator cache is read zero times.IsCacheReadyfollows replicator readiness, and is true outside replicator mode.{"ready": false, "cache_version": "vX"}sets not ready and records vX.cache_versionkeeps the previous one.Against the old code, 6 of these tests fail: the not-ready single and bulk test, the adapter not-ready test, the readiness switch, the ready/flag-not-cached fallback, the missing
cache_versiontest and the timeout test.CI steps run locally with .NET SDK 9.0.109: library and test builds in Release, and
dotnet test, 452 passed. One run hit a failure inSimultaneousFlushes_DoNotInterfere, an event buffer timing test this PR does not touch. It passed on rerun and in the full-suite rerun.🤖 Generated with Claude Code