Skip to content

feat: implement OpenFeature provider InitializeAsync - #203

Merged
jonathannorris merged 5 commits into
mainfrom
feat/openfeature-init
Aug 7, 2026
Merged

feat: implement OpenFeature provider InitializeAsync#203
jonathannorris merged 5 commits into
mainfrom
feat/openfeature-init

Conversation

@jonathannorris

@jonathannorris jonathannorris commented May 4, 2026

Copy link
Copy Markdown
Member

Implements InitializeAsync on DevCycleProvider so Api.Instance.SetProviderAsync(...) waits for DevCycleLocalClient to finish downloading its initial config. Without this, early flag evaluations after SetProviderAsync return defaults instead of targeted values. Other DevCycle SDKs already do this (Node awaits onClientInitialized(), Java/Python poll with a 2s timeout).

DevCycleBaseClient gets a virtual Task InitializeAsync(CancellationToken) returning Task.CompletedTask by default (Cloud is a no-op). DevCycleLocalClient overrides it to await the init task that was previously fired-and-forgotten in the constructor. The cancellation path uses WhenAny + TaskCompletionSource to stay compatible with netstandard2.0, where Task.WaitAsync(CancellationToken) isn't available.

@jonathannorris
jonathannorris requested a review from a team as a code owner May 4, 2026 17:29
Copilot AI review requested due to automatic review settings May 4, 2026 17:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the SDK’s OpenFeature integration so Api.Instance.SetProviderAsync(...) can await the DevCycle Local client’s initial configuration download, preventing early evaluations from returning defaults. It also bumps OpenFeature to 2.13.0 and aligns dependent packages/framework targets to satisfy the newer dependency requirements.

Changes:

  • Implement OpenFeature provider InitializeAsync and add a DevCycleBaseClient.InitializeAsync(...) hook; DevCycleLocalClient now awaits the initial config download task.
  • Upgrade OpenFeature to 2.13.0 and bump related dependencies (Microsoft.Extensions.Logging.*, System.Text.Json) to 10.0.0.
  • Move benchmark/test projects to net10.0 and add a regression test ensuring SetProviderAsync waits for initialization.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
DevCycle.SDK.Server.Local/DevCycle.SDK.Server.Local.csproj Dependency bumps; adds InternalsVisibleTo entry.
DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs Tracks and awaits initial config download via InitializeAsync.
DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs Adds regression test for OpenFeature initialization waiting behavior.
DevCycle.SDK.Server.Local.MSTests/DevCycle.SDK.Server.Local.MSTests.csproj Targets net10.0 and bumps System.Text.Json to 10.0.0.
DevCycle.SDK.Server.Local.Example/DevCycle.SDK.Server.Local.Example.csproj Bumps System.Text.Json to 10.0.0.
DevCycle.SDK.Server.Local.Benchmark/DevCycle.SDK.Server.Local.Benchmark.csproj Targets net10.0 and bumps System.Text.Json to 10.0.0.
DevCycle.SDK.Server.Common/DevCycle.SDK.Server.Common.csproj Bumps OpenFeature to 2.13.0 and updates logging/json dependencies to 10.0.0.
DevCycle.SDK.Server.Common/API/DevCycleProvider.cs Implements InitializeAsync to delegate to client initialization.
DevCycle.SDK.Server.Common/API/DevCycleBaseClient.cs Adds virtual InitializeAsync(...) no-op by default.
DevCycle.SDK.Server.Cloud/DevCycle.SDK.Server.Cloud.csproj Bumps System.Text.Json to 10.0.0.
DevCycle.SDK.Server.Cloud.MSTests/DevCycle.SDK.Server.Cloud.MSTests.csproj Targets net10.0 and bumps System.Text.Json to 10.0.0.
DevCycle.SDK.Server.Cloud.Example/DevCycle.SDK.Server.Cloud.Example.csproj Bumps System.Text.Json to 10.0.0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread DevCycle.SDK.Server.Local/DevCycle.SDK.Server.Local.csproj Outdated
Comment thread DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs Outdated
Comment thread DevCycle.SDK.Server.Local.MSTests/DevCycle.SDK.Server.Local.MSTests.csproj Outdated
Comment thread DevCycle.SDK.Server.Cloud.MSTests/DevCycle.SDK.Server.Cloud.MSTests.csproj Outdated
Comment thread DevCycle.SDK.Server.Local.Benchmark/DevCycle.SDK.Server.Local.Benchmark.csproj Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread DevCycle.SDK.Server.Local/DevCycle.SDK.Server.Local.csproj
Comment thread DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs Outdated
Copilot AI review requested due to automatic review settings May 4, 2026 18:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs
@jonathannorris
jonathannorris force-pushed the feat/openfeature-init branch from 8a21294 to 959db9f Compare August 7, 2026 15:17
@jonathannorris jonathannorris changed the title feat: bump OpenFeature to 2.13.0 and implement provider InitializeAsync feat: implement OpenFeature provider InitializeAsync Aug 7, 2026
Implements InitializeAsync on DevCycleProvider so SetProviderAsync
properly waits for DevCycleLocalClient to finish its initial config
fetch before resolving. Previously, early flag evaluations after
SetProviderAsync returned defaults instead of targeted values.

- DevCycleBaseClient: add virtual InitializeAsync(CancellationToken)
  returning Task.CompletedTask (Cloud is a no-op)
- DevCycleLocalClient: capture init task; override InitializeAsync to
  await it with WhenAny+TaskCompletionSource cancellation (netstandard2.0
  compatible)
- DevCycleProvider: override OpenFeature InitializeAsync, delegating to
  Client.InitializeAsync
- Bump OpenFeature 2.2.0 -> 2.13.0
- Bump Microsoft.Extensions.Logging.* 8.x -> 10.0.0 and System.Text.Json
  8/9.x -> 10.0.0 (required by OpenFeature 2.13.0)
- Update test/benchmark target frameworks net8.0 -> net10.0
- Remove accidental InternalsVisibleTo for OFMultiProviderRepro
- Store InitializeConfigAsync task directly instead of wrapping in Task.Run
…vior

Uses a gated HttpMessageHandler backed by a TaskCompletionSource to hold
the config response. Verifies SetProviderAsync is pending (not completed)
while initialization is blocked, then releases the gate and confirms flag
evaluation returns a real value — not the default.
@jonathannorris
jonathannorris force-pushed the feat/openfeature-init branch from 959db9f to bf1ca04 Compare August 7, 2026 15:18
@jonathannorris

Copy link
Copy Markdown
Member Author

@JamieSinn I closed some of these other PRs, But I think this and #202 still make sense.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

DevCycle.SDK.Server.Local.MSTests/DevCycleTest.cs:636

  • This test doesn't actually verify the new "wait for init" behavior: DevCycleTestClient.getTestClient() pre-populates the config (localBucketing.StoreConfig(...)) and sets configManager.Initialized = true, so flag evaluation will succeed even if SetProviderAsync doesn't wait. To make this regression-proof, the test needs a client whose Initialized starts as false and whose config fetch is delayed until InitializeAsync runs (likely by extending the test helper / mock HTTP setup).
        [TestMethod]
        public async Task TestOpenFeatureProviderWaitsForClientInit()
        {
            using var dvcClient = DevCycleTestClient.getTestClient();
            await OpenFeature.Api.Instance.SetProviderAsync(dvcClient.GetOpenFeatureProvider());
            var ctx = EvaluationContext.Builder().Set("user_id", "j_test").Build();
            var result = await OpenFeature.Api.Instance.GetClient().GetBooleanValueAsync("test", false, ctx);
            Assert.IsTrue(result);
        }

DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs:283

  • The cancellation-wait logic is more complex than necessary. Since Task.WaitAsync isn't available on netstandard2.0, you can still implement cancellable waiting with Task.Delay(Timeout.Infinite, cancellationToken) (available on netstandard2.0) and Task.WhenAny, avoiding a custom TaskCompletionSource + token registration.
        public override async Task InitializeAsync(CancellationToken cancellationToken = default)
        {
            if (cancellationToken.CanBeCanceled)
            {
                var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
                using (cancellationToken.Register(() => tcs.TrySetResult(true)))
                {
                    var completed = await Task.WhenAny(initializeTask, tcs.Task).ConfigureAwait(false);
                    if (completed != initializeTask)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }
                }
            }
            await initializeTask.ConfigureAwait(false);
        }

Comment thread DevCycle.SDK.Server.Local/Api/DevCycleLocalClient.cs
@jonathannorris
jonathannorris enabled auto-merge (squash) August 7, 2026 17:52
@jonathannorris
jonathannorris merged commit 0e78d9f into main Aug 7, 2026
9 checks passed
@jonathannorris
jonathannorris deleted the feat/openfeature-init branch August 7, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants