Repository navigation
feat: Support zero and indefinite initialization waiting - #2024
Conversation
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
@cursor review |
|
@launchdarkly/js-client-sdk size report |
|
@launchdarkly/js-sdk-common size report |
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
|
@launchdarkly/js-client-sdk-common size report |
…n waiting Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
🤖 I have created a release *beep* *boop* --- <details><summary>node-server-sdk-dynamodb: 6.4.0</summary> ## [6.4.0](node-server-sdk-dynamodb-v6.3.3...node-server-sdk-dynamodb-v6.4.0) (2026-10-13) ### Features * Update store peer dep floor to 9.14.1 to better support error reporting and recovery ([#2097](#2097)) ([1369fa4](1369fa4)) </details> <details><summary>node-server-sdk-redis: 4.4.0</summary> ## [4.4.0](node-server-sdk-redis-v4.3.3...node-server-sdk-redis-v4.4.0) (2026-10-13) ### Features * Update store peer dep floor to 9.14.1 to better support error reporting and recovery ([#2097](#2097)) ([1369fa4](1369fa4)) </details> <details><summary>openfeature-cloudflare-server: 1.0.8</summary> ## [1.0.8](openfeature-cloudflare-server-v1.0.7...openfeature-cloudflare-server-v1.0.8) (2026-10-13) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/openfeature-js-server-common bumped from 2.0.6 to 2.1.0 </details> <details><summary>openfeature-js-server-common: 2.1.0</summary> ## [2.1.0](openfeature-js-server-common-v2.0.6...openfeature-js-server-common-v2.1.0) (2026-10-13) ### Features * Support zero and indefinite initialization waiting ([#2024](#2024)) ([8bfd257](8bfd257)) </details> <details><summary>openfeature-node-server: 2.1.0</summary> ## [2.1.0](openfeature-node-server-v2.0.12...openfeature-node-server-v2.1.0) (2026-10-13) ### Features * Support zero and indefinite initialization waiting ([#2024](#2024)) ([8bfd257](8bfd257)) ### Dependencies * The following workspace dependencies were updated * dependencies * @launchdarkly/openfeature-js-server-common bumped from 2.0.6 to 2.1.0 </details> --- 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 Please version bump for five packages: manifests, changelogs, `package.json` versions, and example app dependency pins. > > **`@launchdarkly/openfeature-js-server-common` 2.1.0** (and dependent **`openfeature-node-server` 2.1.0**, **`openfeature-cloudflare-server` 1.0.8**) ship support for **zero** and **`'forever'`** provider initialization timeouts via `initTimeoutSeconds`, in addition to the existing numeric default. Node provider updates its reported `wrapperVersion` to `2.1.0`. > > **`node-server-sdk-dynamodb` 6.4.0** and **`node-server-sdk-redis` 4.4.0** are minor releases documenting a raised **minimum `@launchdarkly/node-server-sdk` peer dependency (≥9.14.1)** for store error reporting and recovery alignment. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4d72062. 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>
**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** Fixes #58. **Describe the solution you've provided** Adds `Provider(String sdkKey, LDConfig config, Duration startWait)` so an application can bound, or deliberately opt out of, waiting for initialization, matching the OpenFeature provider spec (OFP) requirements for start wait behavior: - a positive `Duration` is applied by the LaunchDarkly client constructor, and `initialize` then reports whether the client became ready without waiting a second time - `Duration.ZERO` waits nowhere; `initialize` fails unless the client is already ready, and the application learns when it becomes usable from provider events - `Provider.INFINITE_START_WAIT` requests indefinite waiting: nothing is waited for during construction and `initialize` blocks until the data source becomes valid or fails permanently The existing constructors keep honouring `LDConfig.Builder.startWait(...)` and no longer wait again in `initialize`, so a configured start wait is the only bound on initialization instead of being followed by an unbounded wait. The README feature matrix row and initialization section describe the new behavior. <details> <summary>Implementation details</summary> `initialize` still registers data source and flag change listeners and evaluates the current data source status. Only the indefinite mode leaves the completion future pending; every other mode completes it with the outcome that is already known, so `initialize` returns promptly. The OpenFeature SDK emits its own event for the transition that completes initialization, so the provider records that one state without emitting a duplicate, and emits every state change after it — including a permanent failure that arrives immediately after the data source became valid, and a recovery after a failed initialization. </details> **Describe alternatives you've considered** Keeping the existing indefinite wait and adding a timeout parameter interpreted only inside `initialize` would double-wait when `LDConfig.startWait` is also set, and would not match OFP, which treats the start wait as a bound over construction plus initialization. Java has no built-in infinite `Duration`, so indefinite waiting was initially expressed as `null`. It is now the explicit `Provider.INFINITE_START_WAIT` constant, matching `Timeout.InfiniteTimeSpan` in the .NET provider. **Additional context** Sibling changes for the same OFP requirement: launchdarkly/openfeature-python-server#64, launchdarkly/openfeature-ruby-server#36, launchdarkly/openfeature-dotnet-server#71, and launchdarkly/js-core#2024. Verified with `./gradlew test` (all tests pass, including new coverage for positive, zero, and indefinite start wait). 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](https://launchdarkly.atlassian.net/browse/SDK-3285) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Fixes OpenFeature initialization behavior (**#58**) by adding **`Provider(String, LDConfig, Duration startWait)`** and **`Provider.INFINITE_START_WAIT`**, so startup waiting is bounded in one place instead of blocking again indefinitely inside **`initialize()`** after **`LDConfig.startWait`**. > > **Positive durations** apply during client construction; **`Duration.ZERO`** fails fast unless already ready; **indefinite** mode skips construction blocking and waits in **`initialize()`** until the data source is valid, fails, or the provider shuts down. Oversized durations are treated as indefinite to avoid SDK millis overflow. > > **`initialize()`** is reworked around a completion future and tighter state/event handling: failed init surfaces data-source errors, duplicate **STALE**/**READY** events are deduped, teardown **OFF** does not emit **PROVIDER_ERROR**, and **`shutdown()`** calls **`super.shutdown()`** to stop leaking the event executor. README documents cancellation of indefinite **`setProviderAndWait`** (direct **`provider.shutdown()`** vs API lock contention). > > Lifecycle tests cover bounded/zero/infinite waits, recovery, and classification boundaries. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1c96e64. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --- ## tanderson-ld additional changes Review-driven hardening of the start wait time behavior, on top of the commits above. Summary of the delta: **Indefinite wait is now cancellable.** `shutdown()` completes the initialization future, and the interrupt flag is restored if the waiting thread is interrupted. The README and constructor javadoc document how to cancel an indefinite `setProviderAndWait`: call `provider.shutdown()` directly from another thread, or register with the non-blocking `setProvider`. `OpenFeatureAPI.shutdown()` **cannot** end such a wait — it blocks on the same API lock `setProviderAndWait` holds for the duration of initialization. This lock-contention deadlock is inherent to the OpenFeature Java SDK (it holds the API write lock across a synchronous `initialize()` and contends on it in `shutdown()`), so it is documented rather than worked around; an upstream issue will be filed against the OpenFeature Java SDK. **Start wait time classification / validation.** - Any start wait time too large to represent in milliseconds — including `Provider.INFINITE_START_WAIT` and any other "forever" duration — is classified as indefinite, so it saturates to the indefinite path instead of overflowing when the SDK converts it to milliseconds. (Separately, a too-large duration configured through `LDConfig.Builder.startWait` instead of the constructor still overflows inside the SDK client constructor and leaks the client; that is pre-existing SDK behavior and has been filed as a separate java-core ticket. The sentinel is documented as constructor-argument-only.) - A `null` start wait time falls through to the SDK's default start wait rather than throwing. **Status / event fidelity.** - A failed initialization carries the data source's last error into the thrown exception, so the OpenFeature error event reports the cause rather than a generic message. - The client's `isInitialized()` latch no longer reports `READY` when the data source is currently `OFF`. - Repeated interruptions emit one `PROVIDER_STALE` per state change (state-based dedupe, retained and documented), and stale events now carry the HTTP error detail. **Lifecycle cleanup.** `shutdown()` removes its flag-change and data-source status listeners and calls `super.shutdown()` to terminate the event emitter executor. **Tests.** Added lifecycle coverage for bounded / zero / indefinite waits, failed-init-then-recovery, interruption → stale → ready, single-stale-per-outage dedupe, cancellation of an indefinite `setProviderAndWait` via a direct `provider.shutdown()`, the `OFF`-while-initialized exclusion, listener removal on shutdown, and a direct unit test of the start-wait classification boundary. Full suite and checkstyle pass. [SDK-3285]: https://launchdarkly.atlassian.net/browse/SDK-3285?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Todd Anderson <tanderson@launchdarkly.com>
Gives the OpenFeature providers the initialization waiting values the OpenFeature provider spec (OFP) requires, which the numeric-only
initTimeoutSecondscould not express.initTimeoutSeconds: 'forever'waits indefinitely for the LaunchDarkly clientinitTimeoutSeconds: 0waits nowhere and fails initialization unless the client is already ready, so readiness is observed through provider eventsImplementation details
Requirements
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.initTimeoutSecondsand the Node provider's constructor parameter are nownumber | 'forever', and zero is no longer coalesced into the default, since??only replacesundefined.waitForInitializationtreats 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 | nullwas used initially for the indefinite case;'forever'reads better at the call site and matches the repository's preference for string unions. Addinginitialized()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.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
Note
Overview
Extends
initTimeoutSecondson the sharedBaseOpenFeatureProviderand the NodeLaunchDarklyProvidertonumber | 'forever', aligning initialization with the OpenFeature provider spec.'forever'callswaitForInitialization()with no timeout.0no longer falls through to the 10-second default (onlyundefineddoes); 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.
Reviewed by Cursor Bugbot for commit 87e9a23. Bugbot is set up for automated code reviews on this repo. Configure here.