Skip to content

feat: Support waiting indefinitely for initialization - #36

Draft
kinyoklion wants to merge 4 commits into
mainfrom
devin/1788360613-ruby-zero-start-wait
Draft

kinyoklion wants to merge 4 commits into
mainfrom
devin/1788360613-ruby-zero-start-wait

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 2, 2026 •

Copy link
Copy Markdown
Member

A nil wait time now asks the provider to wait for the LaunchDarkly client without a deadline.

  • wait_for_seconds: nil makes init wait until the data source becomes valid or fails permanently; the client constructor does not block.
  • A wait time of zero keeps its duration meaning: nothing waits, and init reports a failed initialization unless the client is already ready.
  • A positive wait time is unchanged; the client constructor applies it and init does not wait a second time.
  • Follows the initialization requirements of the OpenFeature provider behavior spec, which requires a value distinct from any duration to request indefinite waiting. Matches the Python provider.

Found during the weekly OpenFeature provider audit. Updated from the earlier revision of this PR, which overloaded a zero wait time to mean "wait indefinitely".

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 provider keeps the wait time it was constructed with and passes zero to the LaunchDarkly client constructor when it is nil, so construction never blocks in that case. When the wait time is nil, init subscribes a DataSourceOutcomeListener to the data source status provider, then checks whether the client is already initialized or the data source is already OFF before blocking on a queue, so an outcome reached between construction and subscription is not missed. The listener is removed once an outcome arrives. VALID and OFF decide the outcome; INITIALIZING and INTERRUPTED leave the client still trying, so they do not end the wait.

Whether initialization succeeded is still decided by initialized?, and a failure still raises, which the OpenFeature SDK turns into the ERROR state. A failed initialization is not terminal: status changes keep flowing, so a later VALID state reports the provider as ready.

Describe alternatives you've considered

Applying a default timeout for nil: nil is the caller asking for no deadline, and the OpenFeature SDK decides how long to wait for a provider.

Polling initialized?: the data source status provider already reports transitions, so polling would only add latency and wake-ups.

Additional context

Testing: bundle exec rspec (80 examples) and bundle exec rubocop on Ruby 3.4.5. Tests cover an indefinite wait succeeding once the data source becomes valid, an indefinite wait for an outcome arriving after init is called, a zero wait time not waiting at all, a positive wait time not waiting a second time, and the outcome listener's per-state behavior.

The README's Initialization row describes the nil behavior.

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

SDK-3285

@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot added the devin-pr PRs created by Devin label Sep 2, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

@cursor review

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

Copy link
Copy Markdown
Contributor

@cursor review

devin-ai-integration Bot and others added 2 commits September 29, 2026 22:56
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
The provider's data source status and flag change listeners stayed
registered on the LaunchDarkly client after `shutdown`, so a provider
which the OpenFeature SDK had already replaced could still emit events.
They are now removed before the client is closed.

Event deduplication and the events emitted after a failed
initialization, which this PR previously carried, now live in
[#36](#36)
instead; this branch has been merged with that one and keeps only the
`shutdown` change. Review
[#36](#36)
first.

Found during the weekly OpenFeature provider audit.

<details>
<summary>Implementation details</summary>

**Requirements**

- [x] 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)
- [x] I have validated my changes against all supported platform
versions

**Related issues**

None.

**Describe the solution you've provided**

The listeners the constructor registers are kept on the provider so that
`shutdown` can pass them to `remove_listener` on the data source status
provider and the flag tracker before closing the client.

**Describe alternatives you've considered**

Relying on `close` alone: the LaunchDarkly client stops notifying
listeners once it is closed, but the provider is the owner of those
subscriptions and a closed client is not a guarantee the OpenFeature SDK
makes about a replaced provider.

**Additional context**

Testing: `bundle exec rspec` (83 examples) and `bundle exec rubocop` on
Ruby 3.4.5. The `shutdown` spec now asserts both listeners are removed
as well as the client being closed.
</details>

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

---------

Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Devin AI <devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
kinyoklion added a commit to launchdarkly/js-core that referenced this pull request Oct 9, 2026
Gives the OpenFeature providers the initialization waiting values the
OpenFeature provider spec (OFP) requires, which the numeric-only
`initTimeoutSeconds` could not express.

- `initTimeoutSeconds: 'forever'` waits indefinitely for the
LaunchDarkly client
- `initTimeoutSeconds: 0` waits nowhere and fails initialization unless
the client is already ready, so readiness is observed through provider
events
- A positive timeout behaves as before, and the default is still 10
seconds
- Node provider README feature matrix row updated for the new values

<details>
<summary>Implementation details</summary>

**Requirements**

- [x] 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)
- [x] I have validated my changes against all supported platform
versions

**Related issues**

OFP requirement 4.3.4 requires a value distinct from any duration to
request waiting indefinitely, and 4.3.6 requires a zero wait to wait
nowhere and fail initialization. Sibling changes for the same
requirement: launchdarkly/openfeature-java-server#66,
launchdarkly/openfeature-python-server#64,
launchdarkly/openfeature-dotnet-server#71,
launchdarkly/openfeature-ruby-server#36.

**Describe the solution you've provided**

`BaseProviderConfig.initTimeoutSeconds` and the Node provider's
constructor parameter are now `number | 'forever'`, and zero is no
longer coalesced into the default, since `??` only replaces `undefined`.
`waitForInitialization` treats a falsy timeout as no timeout, so zero
cannot be passed through: initialization races the pending
initialization promise against an immediately rejected one, which
succeeds only when the client is already ready.

The Cloudflare provider is unaffected because it evaluates from a KV
namespace and has no data source to wait for.

**Describe alternatives you've considered**

`number | null` was used initially for the indefinite case; `'forever'`
reads better at the call site and matches the repository's preference
for string unions. Adding `initialized()` to the client contract would
let the zero case be checked directly, but that is a breaking change to
a published interface for behavior the existing promise already
expresses.

**Additional context**

Verified with `yarn workspace @launchdarkly/openfeature-js-server-common
test` (82 tests), `yarn workspace @launchdarkly/openfeature-node-server
test` (29 tests), and lint, format and build checks for both packages.

</details>

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Extends **`initTimeoutSeconds`** on the shared
**`BaseOpenFeatureProvider`** and the Node **`LaunchDarklyProvider`** to
**`number | 'forever'`**, aligning initialization with the OpenFeature
provider spec.
> 
> **`'forever'`** calls **`waitForInitialization()`** with no timeout.
**`0`** no longer falls through to the 10-second default (only
**`undefined`** does); initialization races the client’s init promise
against an immediate failure so it succeeds only when the client is
already ready, otherwise it throws a clear error. Positive timeouts
behave as before (default **10** seconds).
> 
> The Node provider README initialization row documents the new values,
and unit tests cover indefinite wait, zero-timeout failure, and
zero-timeout success when already ready.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
87e9a23. 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: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devin-pr PRs created by Devin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant