feat(feature-flags): add UFC parser and agentless HTTP poller - #9042
Conversation
BenchmarksBenchmark execution time: 2026-08-27 13:37:24 Comparing candidate commit da002db in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 72 metrics, 0 unstable metrics, 68 known flaky benchmarks, 58 flaky benchmarks without significant changes.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1357754452
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (9042) and master. ✅ No regressions detected |
…lformed sites safely
…nd validation telemetry
… sensitive in yaml
…b.com:DataDog/dd-trace-dotnet into pavlo.khrebto/EX-2703/ffe-agentless-poller
… retry, ETag, gzip, and shutdown
…ptured at startup
24eea14 to
1717780
Compare
…o.khrebto/EX-2703/ffe-agentless-poller
…om:DataDog/dd-trace-dotnet into pavlo.khrebto/EX-2703/ffe-agentless-poller # Conflicts: # tracer/src/Datadog.Trace/FeatureFlags/Agentless/AgentlessConfigurationSource.cs # tracer/test/Datadog.Trace.Tests/FeatureFlags/AgentlessConfigurationSourceTests.cs
…InitializeAsync (#9044) ## Summary of changes Wires the agentless poller and RC subscription into `FeatureFlagsModule` with source-agnostic activation, and adds `InitializeAsync` through the OpenFeature provider lifecycle so delivery starts when application code initializes the provider. This is PR 3 of a stacked PR series implementing agentless Feature Flags configuration delivery (FFL-2703), porting functionality from [dd-trace-py#19331](DataDog/dd-trace-py#19331) and [dd-trace-java#11892](DataDog/dd-trace-java#11892). Stacked on [PR 1 #9040](#9040) and [PR 2 #9042](#9042). ## Reason for change PR 1 added the configuration layer and PR 2 added the poller and parser. This PR connects them: the module now selects the delivery source at construction time, starts polling only when the provider is initialized, and waits for the first configuration before returning from `InitializeAsync`. ## Implementation details - **`TracerSettings`** — replaces `IsFlaggingProviderEnabled` with `FeatureFlagsSettings FeatureFlags` property - **`FeatureFlagsModule`** — source-agnostic constructor: - RC subscription only when `Source == RemoteConfig` (gated, no longer always-on) - `Create()` checks `settings.FeatureFlags.Enabled` - `Activate()` starts the agentless poller or marks RC intent, with `_deliveryStarted` tracking - `InitializeAsync()` calls `Activate()`, waits for `FirstConfigReceived` with timeout, skips the wait if no delivery started - `ApplyConfiguration()` — public method used by both agentless and RC paths, signals `FirstConfigReceived` - `Dispose()` now also disposes the agentless source and unsubscribes RC - **`OpenFeatureSdkInitializeAsyncIntegration`** (new CallTarget) — intercepts `FeatureFlagsSdk.InitializeAsync` stub and calls `FeatureFlagsModule.InitializeAsync` - **`DatadogProvider.InitializeAsync`** — override that delegates to `FeatureFlagsSdk.InitializeAsync` - **`FeatureFlagsSdk.InitializeAsync`** — no-op stub in the OpenFeature package - **`supported_calltargets.g.json`** — registers the new integration - **`SpanEnrichmentIntegrationTests`** — suppresses `[Obsolete]` warning on `FlaggingProviderEnabled` ## Test coverage - **`FeatureFlagsModuleTests`** (9 tests) — RC config update/reset, agentless source doesn't subscribe to RC, RC source subscribes and advertises capability, disabled returns null, `InitializeAsync` returns immediately when config already applied, returns when config arrives while waiting, returns on timeout without throwing, returns on cancellation without throwing, returns immediately when agentless source cannot start (no API key) ## Other details <!-- Fixes #{issue} --> Stacked PRs: - PR 1 [#9040](#9040): Configuration keys, settings, and endpoint derivation - PR 2 [#9042](#9042): UFC parser and agentless HTTP poller - PR 3 (this): Module wiring and OpenFeature activation - PR 4: Manual API activation <!--⚠️ Note: Where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. MergeQueue is NOT enabled in this repository. If you have write access to the repo, the PR has 1-2 approvals (see above), and all of the required checks have passed, you can use the Squash and Merge button to merge the PR. If you don't have write access, or you need help, reach out in the #apm-dotnet channel in Slack. --> System tests: [DataDog/system-tests#7496](DataDog/system-tests#7496)
Summary of changes
Adds the agentless delivery mechanism: a
UfcConfigurationParserthat validates the JSON:API envelope returned by the CDN endpoint, and anAgentlessConfigurationSourceHTTP poller with retries, ETag caching, gzip decompression, and shutdown safety.This is PR 2 of a stacked PR series implementing agentless Feature Flags configuration delivery (FFL-2703), porting functionality from dd-trace-py#19331 and dd-trace-java#11892. Stacked on PR 1 #9040.
Reason for change
PR 1 added the configuration layer (settings, endpoint derivation). This PR adds the actual HTTP delivery mechanism that polls the CDN endpoint for UFC flag configuration. It is self-contained: the poller and parser are fully testable in isolation and are not yet wired to the
FeatureFlagsModule(that happens in PR 3).Implementation details
UfcConfigurationParser— validates the JSON:API Universal Flag Configuration response envelope, extractsdata.attributesintoServerConfiguration. Rejects raw UFC documents (without the JSON:API wrapper) so all delivery sources agree on one wire format. Returns descriptive errors; never throws.AgentlessConfigurationSource— HTTP poller with:clamp(P/6, 2s, 10s)thenclamp(P/3, 5s, 30s))If-None-Matchconditional polling; ETag advances only after parse + apply succeedx-datadog-tracing-enabled: falseheader)CancellationTokenSourcecancels the loop; shutdown checks after request and beforeApplyAsyncprevent use-after-disposeCreate()factory returnsnullwhen endpoint can'"'t be built or API key is missingStart()viaInterlocked.CompareExchangeTest coverage
UfcConfigurationParserTests(16 tests) — valid envelope parsing, malformed JSON rejection, invalid envelope rejection (wrong type, missing data, raw UFC), invalid attributes rejection (missing format/createdAt/environment/flags, wrong types), flag parsingAgentlessConfigurationSourceTests(12 tests) — 200 success, ETag forwarding, 304/401/malformed handling, disposal before/after request, retry on 500 then success, max retries on 500, no retry on 400, gzip response, network error, idempotent startOther details
Stacked PRs:
System tests: DataDog/system-tests#7496