fix(openfeature): make InitWithContext return ready without a first config#5053
fix(openfeature): make InitWithContext return ready without a first config#5053vjfridge wants to merge 1 commit into
Conversation
…onfig InitWithContext previously blocked on `configuration != nil` until the caller's ctx expired or the internal 30s timeout fired. That was wrong: the provider is ready as soon as its RC subscription is established (SubscribeProvider / AttachCallback in startWithRemoteConfig run synchronously before Init), not when the first config payload arrives. When the agent's initial RC response for a subscribed FFE_FLAGS client omits the product — either because no configs are targeted at the service, or because a container restart lands during a poll window where the sidecar has nothing cached — no callback ever fires. Init then consumed its full deadline. Combined with the recent rapid framework change setting resizePolicy: RestartContainer on staging services with vertical autoscaling (dd-source 6af559ea4e02d), every DPA-driven in-place resize started re-triggering the wait and blowing past the 30s startup-probe budget, sending ffe-service pods into CrashLoopBackOff on every applied recommendation. Init now waits opportunistically up to a bounded initialConfigWait (5s) so fresh deployments still block briefly for real flag data on the happy path, but returns successfully with no error if the wait expires or the caller's ctx is cancelled first. Flag evaluations before the first config resolve to caller-supplied defaults with reason DEFAULT; when the config arrives via the RC callback, evaluations pick it up without any restart or reinit. Updated TestSetProviderWithContextAndWaitTimeout — which encoded the old buggy behavior — into two tests: one asserting Init returns without error when no config arrives and honors the caller ctx, one asserting Init unblocks as soon as a config arrives during the wait. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 18ba72f | Docs | Datadog PR Page | Give us feedback! |
BenchmarksBenchmark execution time: 2026-07-21 17:12:45 Comparing candidate commit 18ba72f in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 325 metrics, 1 unstable metrics, 1 flaky benchmarks without significant changes.
|
What
Change
openfeature.DatadogProvider.InitWithContextso it does not block indefinitely waiting for the first Remote Config payload. Init now performs a short, bounded opportunistic wait (initialConfigWait = 5s) for the first config, and returnsnil(ready) whether or not that config arrived — flag evaluations pre-config resolve to caller-supplied defaults with reasonDEFAULTand pick up the real config as soon as the RC callback fires.Why
InitWithContextpreviously looped onconfiguration != nil, waiting onconfigChange.Broadcastand bounded only by the caller's ctx (orInit's own 30s timeout when called without a ctx). That treated "the agent has not yet delivered a config for FFE_FLAGS" as an initialization failure, but it's a legitimate steady state:The RC subscription being established is a stronger readiness signal than a config arriving — the subscription is a precondition for reaching
InitWithContextat all (seestartWithRemoteConfigcallingSubscribeProvider/AttachCallbacksynchronously).Observed failure on ffe-service in staging (gizmo): every DPA-driven vertical in-place resize triggered a main-container restart (staging-only rapid change
6af559ea4e02dsetresizePolicy: RestartContainerfor services with vertical autoscaling). The new Go process re-enteredInitWithContext; the sidecar's response for that fresh subscription contained no FFE_FLAGS payload;configChange.Broadcastnever fired; the container was killed by the startup probe at 30s. Logs on the crashing pods show"openfeature-datadog: Init called"at t=0,"openfeature-datadog: Init completed successfully"never, and the container exiting exactly 30s later — repeated across every restart cycle until the DPA was patched to Preview.How
InitWithContextwrapsctxincontext.WithTimeout(ctx, initialConfigWait)and callswaitForConfigurationUpdateonce. Any error (deadline, cancel) is intentionally swallowed.initialConfigWait = 5s— long enough to cover a normalPollInterval-bounded delivery on the happy path, short enough that a service with an idle RC feed still comes ready inside a typical startup-probe budget, well belowdefaultInitTimeoutso callers passingcontext.Background()don't collide with an outer probe deadline.Existing behavior preserved:
ctxdeadline is shorter thaninitialConfigWait, we honor the caller's deadline.TestSetProviderWithContextAndWaitConfigArrivesDuringInit).Init()(no-ctx form) still usesdefaultInitTimeoutas its outer bound; the change is what happens within that budget.Tests
TestSetProviderWithContextAndWaitTimeout→TestSetProviderWithContextAndWaitNoConfig. Old test asserted the buggy behavior (Init returnscontext.DeadlineExceededwhen no config arrives). New test asserts Init returnsnilinside the caller's deadline.TestSetProviderWithContextAndWaitConfigArrivesDuringInit— Init unblocks immediately when a config arrives during the opportunistic wait, and the config is visible on the provider afterward../openfeature/...and./internal/openfeature/...suites pass with-race.Follow-ups (out of scope)
configChange.Waitgoroutine started bywaitForConfigurationUpdateis not cancelable by ctx — it only exits when a broadcast eventually fires. Pre-existing, not introduced by this PR. BecauseInitruns at most once per process, this leaks at most one goroutine until the first config eventually arrives; acceptable but worth cleaning up separately.resizePolicy: RestartContainer) should be reviewed jointly — any service with a startup-probe budget ≤ 30s that depends on RC-delivered config at startup is exposed to the same class of failure. This PR removes the OpenFeature-specific exposure; a broader audit of what else blocks on RC at startup is the Rapid team's call.Internal tracking: FFL-2809.