Skip to content

fix: Honor the cancellation token when waiting for initialization - #72

Merged
kinyoklion merged 3 commits into
mainfrom
devin/1790174787-dotnet-init-cancellation
Oct 8, 2026
Merged

kinyoklion merged 3 commits into
mainfrom
devin/1790174787-dotnet-init-cancellation

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

InitializeAsync ignored its CancellationToken, so a caller that passed a token to SetProviderAsync could not stop waiting for the LaunchDarkly client.

  • Cancelling the token now completes InitializeAsync with an OperationCanceledException instead of waiting for the data source
  • Cancellation is per-call: cancelling one wait leaves the provider initializing and does not fail other waiters
  • Feature matrix Initialization row updated to describe the behavior

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

None.

Implementation details

The OpenFeature .NET SDK passes the token given to Api.SetProviderAsync(provider, cancellationToken) through ProviderRepository into FeatureProvider.InitializeAsync. The provider returned _initCompletion.Task directly, so the token had no effect.

Waiting now goes through a helper which, when the token can be canceled, races _initCompletion.Task against a TaskCompletionSource canceled by a token registration. _initCompletion itself is untouched, so the provider still becomes READY (or errors) when the data source reports its state, regardless of a canceled wait.

Shutdown is unchanged: it only removes listeners and disposes the client, so there is no wait to cancel.

Tests: two cases covering cancellation of a first wait and of a wait on an already in-progress initialization. dotnet test -f net8.0 passes (70 tests).

Note: #71 also changes initialization waiting; whichever merges second needs a trivial rebase of this region.

Link to Devin session: https://app.devin.ai/sessions/a47abf28ecd44130917b9b5131287221
Open in Devin Desktop: https://app.devin.ai/desktop/session/a47abf28ecd44130917b9b5131287221?variant=devin
Requested by: @kinyoklion

SDK-3255


Note

Overview
InitializeAsync now honors the CancellationToken passed through OpenFeature (e.g. from SetProviderAsync), so callers can stop waiting for the LaunchDarkly data source without blocking until ready or permanent failure.

Waiting goes through WaitForInitializationAsync, which races _initCompletion against token cancellation. Cancellation is per call: _initCompletion is unchanged, so the client can still finish initializing and other waiters are unaffected. A canceled wait completes with OperationCanceledException.

StatusProvider gains StartupWaitCanceled: it clears the “first event” suppression and, if status is already known, emits the matching provider event so subscribers still see READY (or stale/error) after a canceled startup wait. Status messages are retained for that replay; event emission is centralized in EmitStatusEvent.

The README Initialization row and three unit tests document and cover token cancel on first wait, cancel on a concurrent second wait, and ProviderReady after cancel when the data source later becomes valid.

Reviewed by Cursor Bugbot for commit afef99f. Bugbot is set up for automated code reviews on this repo. Configure here.

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Contributor

@cursor review

@kinyoklion
kinyoklion marked this pull request as ready for review September 29, 2026 22:42
@kinyoklion
kinyoklion requested a review from a team as a code owner September 29, 2026 22:42

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fb037c7. Configure here.

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
@kinyoklion
kinyoklion merged commit 0e6432d into main Oct 8, 2026
10 checks passed
@kinyoklion
kinyoklion deleted the devin/1790174787-dotnet-init-cancellation branch October 8, 2026 22:53
kinyoklion added a commit that referenced this pull request Oct 23, 2026
LaunchDarkly .NET SDK 8.17.0 made a rejected SDK key (401/403 and other
4xx) retryable, so the data source no longer reaches `Off` for it. The
README matrix still said `InitializeAsync` waits until the client
becomes ready "or fails permanently", which is no longer true for a
rejected key.

- Initialization row: now ⚠️. It notes the SDK 8.17.0 behavior: without
a cancellation token, `InitializeAsync` can wait indefinitely. It links
the provider-side tracking PR
[#71](#71)
(start wait).
- Asynchronous Initialization: the SDK's default `StartWaitTime` is 10
seconds (`ConfigurationBuilder.DefaultStartWaitTime`). The README said
5.

<details>
<summary>Details</summary>

**Requirements**

- [ ] I have added test coverage for new or changed functionality
- [x] I have followed the repository's [pull request submission
guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests)
- [ ] I have validated my changes against all supported platform
versions

**Related issues**

#71

**Describe the solution you've provided**

The provider references `LaunchDarkly.ServerSdk` `[8.1.0,9.0)`, so new
installs resolve to 8.17.x and get the retry behavior. 8.17.0 release
notes: "`DataSourceState.Off` no longer indicates a rejected SDK key.
`Off` is now reached only by explicit shutdown."

`main` was merged in after #72 (cancellation token) landed. The row now
keeps #72's cancellation wording and no longer links #72 as a gap.

**Additional context**

#71 also edits the Initialization row. Whichever merges second will need
a small README conflict fix.

Found in the weekly OpenFeature provider audit. Docs-only change.
</details>

Link to Devin session:
https://app.devin.ai/sessions/2a67f9d291bc4417bb40f632c191b44f
Open in Devin Desktop:
https://app.devin.ai/desktop/session/2a67f9d291bc4417bb40f632c191b44f?variant=devin
Requested by: @kinyoklion

[SDK-3255](https://launchdarkly.atlassian.net/browse/SDK-3255)
<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Updates the README **Initialization** feature matrix row to **partial
(⚠️)** and revises how `InitializeAsync` behaves with LaunchDarkly .NET
SDK **8.17.0+**: a rejected SDK key is retried instead of failing
permanently, so without a `SetProviderAsync` cancellation token
initialization can wait indefinitely. The row now says init waits until
ready or shutdown (not permanent failure) and links provider follow-up
**#71**.
> 
> In **Asynchronous Initialization**, the documented default constructor
blocking wait is corrected from **5** to **10** seconds to match
`ConfigurationBuilder.DefaultStartWaitTime`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7920750. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

[SDK-3255]:
https://launchdarkly.atlassian.net/browse/SDK-3255?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
kinyoklion pushed a commit that referenced this pull request Oct 23, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.3.2](2.3.1...2.3.2)
(2026-10-23)


### Bug Fixes

* Honor the cancellation token when waiting for initialization
([#72](#72))
([0e6432d](0e6432d))
* Include an error type in provider error events
([#73](#73))
([4f64d9c](4f64d9c))
* Include the last data source error in provider error and stale events
([#74](#74))
([8c23f97](8c23f97))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Release 2.3.2** bumps the package version in the manifest,
`LaunchDarkly.OpenFeature.ServerProvider.csproj`, and adds a **2.3.2**
section to `CHANGELOG.md`. No application code changes appear in this
PR; it ships three already-merged bug fixes.
> 
> The release notes call out: honoring **`CancellationToken`** during
provider initialization waits, setting **`ErrorType`** on provider error
events, and including the **last data source error** in provider error
and stale event messages.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
47ba220. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants