[FeatureFlags] Wait for OpenFeature initial config - #9093
Conversation
Addresses two inline review comments from dromanol on PR #8754. IsReadyIntegration (comment 3389306667): Before: featureFlags is null || featureFlags.IsReady() After: featureFlags?.IsReady() ?? false FeatureFlagsModule.IsReady (comment 3389322766): Before: !_isRemoteConfigurationAvailable || evaluator != null After: _isRemoteConfigurationAvailable && evaluator != null FATAL on uninstrumented tracer (Java parity): InitializeAsync now throws ProviderFatalException immediately when IsAvailable() is false (tracer not instrumented). Mirrors Java's Provider.initialize(): FatalError when DDEvaluator cannot be loaded. Behavior: Tracer not instrumented → ProviderFatalException (FATAL, immediate) Tracer running, RC off → 30s timeout → ProviderNotReadyException Tracer running, RC on → waits for config → READY No new calltargets or public API. IsAvailable() is already hooked. Jira: FFL-2468
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (9093) and master. ✅ No regressions detected |
BenchmarksBenchmark execution time: 2026-08-21 19:28:21 Comparing candidate commit 99b317a in PR branch Found 0 performance improvements and 2 performance regressions! Performance is the same for 70 metrics, 0 unstable metrics, 68 known flaky benchmarks, 58 flaky benchmarks without significant changes.
|
IsReady() now returns false when RC is disabled (_isRemoteConfigurationAvailable && evaluator != null), so the test expectation must be false, not true. The provider handles this via InitializeAsync throwing ProviderFatalException when IsAvailable() returns false (tracer not instrumented). Jira: FFL-2468
- Fix stale type name in OpenFeatureSdkIsReadyIntegration doc comment (was Datadog.Trace.FeatureFlags, should be Datadog.FeatureFlags.OpenFeature) - Add [DoesNotReturn] to ThrowInitialConfigTimeout so analyzers suppress spurious unreachable-code and null-ref warnings at call sites - Cancel the 30s Task.Delay timer via CancellationTokenSource when initialization succeeds or is cancelled, eliminating orphaned timer objects - Use Volatile.Write for _onNewConfigEventHander assignment to close the ordering gap between RC thread and registration thread; also invoke the local parameter directly rather than re-reading the field - Add IsReady_WhenEvaluatorInstalledAndRCEnabled_ReturnsTrue and RegisterOnNewConfigEventHandler_ReplaysFiredWhenEvaluatorAlreadyPresent unit tests to improve coverage of IsReady() and replay paths Note: ProviderFatalException path in InitializeAsync is not unit-testable from Datadog.Trace.Tests due to System.Diagnostics.DiagnosticSource version conflict with Datadog.FeatureFlags.OpenFeature; covered by integration tests. Jira: FFL-2468
…y flag The _isRemoteConfigurationAvailable guard in IsReady() caused false negatives in environments where TracerSettings.IsRemoteConfigurationAvailable returns false even though RC is functional (observed on net48/x86 integration tests). The guard is redundant: when RC is truly disabled, no payload arrives and the evaluator stays null anyway. IsReady() = evaluator != null is sufficient for all cases: - RC enabled + evaluator installed → true (READY) - RC enabled + no evaluator yet → false (wait) - RC disabled → false (wait → 30s timeout) - No tracer → IsAvailable() = false → FATAL Also renames the unit test to reflect the corrected semantic. Jira: FFL-2468
…ilability flag" This reverts commit 27f5fee.
…ags state Temporary diagnostic instrumentation to identify root cause of ProviderReadyWaitsForInitialConfig failure. IsReadyIntegration calltarget now logs to stderr: - Whether TracerManager.Instance.FeatureFlags is null - _isRemoteConfigurationAvailable value - Whether evaluator is installed DatadogProvider.InitializeAsync now logs to stderr: - IsReady() and IsAvailable() values on entry - Which branch is taken (CompletedTask / FATAL / WaitForInitialConfig) Stderr output does not affect the test's stdout assertions. Remove this commit once root cause is identified. Jira: FFL-2468
Log timestamp, list count, and handler null status on every UpdateRemoteConfig call to understand why configUpdates stays 0 and when evaluator is installed/cleared relative to SetProviderAsync. Jira: FFL-2468
Motivation
FFL-2468 captures a customer-visible startup race in the .NET OpenFeature provider. `await SetProviderAsync(new DatadogProvider())` could report the provider as ready before the first FFE Remote Config payload had installed an evaluator, so an immediate flag evaluation returned `PROVIDER_NOT_READY`. This made `DD_TRACE_DEBUG=true` appear necessary because debug logging slowed startup enough for Remote Config to arrive first.
Changes
This adds an OpenFeature readiness shim and calltarget hook so the provider can ask the tracer whether Feature Flags are ready. `DatadogProvider.InitializeAsync` now waits for the initial usable FFE configuration before completing provider initialization. If that first configuration never arrives, initialization fails after 30 seconds with a provider-not-ready timeout instead of completing as ready or blocking forever.
The Feature Flags module now exposes readiness based on whether an evaluator has been installed, replays the config callback if the evaluator already exists when the handler is registered, and avoids blocking provider initialization when Remote Config is explicitly unavailable. Later Remote Config updates still flow through the existing configuration-change callback path.
Additionally, `InitializeAsync` now fails immediately with `ProviderFatalException` when the Datadog tracer is not instrumented (`IsAvailable()` returns false). This mirrors the Java provider's behavior: if the tracer agent is absent, initialization is unrecoverable rather than timing out after 30 seconds.
Decisions
The initialization behavior deliberately mirrors the Java OpenFeature provider across all three states:
The fix treats an awaited `SetProviderAsync(...)` as a readiness contract because the OpenFeature SDK documents the returned task as waiting for provider setup and initialization to complete. A caller that intentionally does not await the task can still run async startup, but once the task completes successfully, Datadog should not immediately return `PROVIDER_NOT_READY` because the initial FFE config has not been applied yet.
The timeout is bounded at the provider layer rather than changing evaluation semantics. If there is no evaluator during evaluation, evaluations still report `PROVIDER_NOT_READY`; the provider just no longer advertises readiness before the evaluator exists. Generated calltarget metadata was updated for the new `IsReady` hook.
`IsReady()` uses `_isRemoteConfigurationAvailable && evaluator != null` rather than the inverse form, so a provider with RC explicitly disabled does not falsely report ready. If RC is disabled, the provider waits up to 30 seconds then fails with `ProviderNotReadyException`, consistent with the Java provider's timeout path.
Validation
Not run locally: