FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat: Add a start wait timeout for initialization by kinyoklion · Pull Request #61 · launchdarkly/openfeature-java-server · GitHub

feat: Add a start wait timeout for initialization - #61

Open
kinyoklion wants to merge 7 commits into
mainfrom
devin/1787767004-java-start-wait
Open

feat: Add a start wait timeout for initialization#61
kinyoklion wants to merge 7 commits into
mainfrom
devin/1787767004-java-start-wait

Conversation

kinyoklion commented Aug 26, 2026
edited by cursor Bot
Loading

Copy link
Copy Markdown
Member

initialize blocked on CompletableFuture.get() with no timeout, so a provider whose data source never became valid and never permanently failed would wait forever.

Closes #58.

  • Adds Provider(String sdkKey, LDConfig config, Duration startWait), which applies the duration to the SDK through LDConfig.startWait
  • That duration bounds the whole of initialization once: the constructor blocks for up to that long, and initialize then reports whatever the outcome already is instead of waiting a second time
  • If the client did not become ready in time, the provider goes to ERROR and initialize throws, distinct from the existing initialization-failure path
  • A zero duration means no provider-applied timeout, per OFP 4.3.4.2: the constructor does not block and initialize waits until the data source is valid or permanently failed
  • The existing constructors are unchanged in behavior: they leave the caller's LDConfig.startWait untouched, so a caller who configured startWait themselves is not overridden
  • Parameter naming follows LDConfig.startWait rather than inventing a cross-provider name
Implementation details

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

#58

Describe the solution you've provided

LDClient only exposes one- and two-argument constructors and LDConfig has no start-wait getter, so the three-argument constructor applies the duration through LDConfig.Builder.startWait(...) and keeps its own copy. Because the SDK constructor has already consumed that budget by the time initialize runs, initialize reads the completed state with getNow(false) rather than waiting again — a single wait, not two back-to-back ones.

The lack of a start-wait getter is also why the two-argument constructor stays indefinite rather than adopting a default: it cannot discover a start wait the caller configured, so defaulting would silently override it.

Describe alternatives you've considered

Making the existing constructors default to the SDK's five second start wait would have given every caller a timeout without an API change, but it overrides a startWait the caller set on their own LDConfig — which the README tells them to do — so it was rejected as a silent behavior change.

Having initialize run its own timed wait on top of the SDK's was the first implementation; it meant a caller passing a two second start wait could wait four seconds in total, so the provider now reuses the wait the SDK already performed.

Additional context

Testing: ./gradlew test checkstyleMain javadoc — all tests, checkstyle and javadoc pass. Lifecycle tests cover a positive start wait failing without waiting again against a data source that never becomes ready, a client that becomes ready during the start wait, a zero start wait waiting indefinitely, and the two-argument constructor preserving a configured start wait.

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


Note

Overview
Adds Provider(String, LDConfig, Duration) so callers can cap how long LaunchDarkly client startup blocks: the duration is applied via LDConfig.startWait, the constructor consumes that wait once, and initialize finishes from the current data-source state instead of blocking again.

When startWait is positive and the client never becomes ready in time, initialize sets ERROR and throws with a distinct timeout message (separate from permanent init failure). Duration.ZERO keeps indefinite waiting in initialize; the existing one- and two-argument constructors are documented to leave config startWait alone and still wait indefinitely at the provider layer. Wrapper injection is refactored through withWrapper, and lifecycle tests cover timeout-without-double-wait, success during wait, failure after wait, zero-duration indefinite wait, and preserving config start wait on the two-arg constructor.

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

devin-ai-integration Bot and others added 3 commits August 26, 2026 17:59
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>

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

Copy link
Copy Markdown
Contributor

@cursor review

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
kinyoklion marked this pull request as ready for review August 27, 2026 22:00
kinyoklion requested a review from a team as a code owner August 27, 2026 22:00
*/
public Provider(String sdkKey) {
this(sdkKey, new LDConfig.Builder().build());
this(new LDClient(sdkKey, withWrapper(new LDConfig.Builder().build())), Duration.ZERO);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I think Duration.Zero meaning wait indefinitely is not consistent with Duration.Zero passed to other SDKs startup/init/wait APIs. I think Duration.Zero is usually interpreted as don't wait at all.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Zero here came from OFP 4.3.4.2 — "If the configured start wait time is zero, the provider MUST NOT apply an initialization timeout" — with the rationale that zero means the application does not want to block on initialization and leaves how long to wait up to the caller.

Worth separating the two layers, because I think my javadoc is what actually reads wrong:

  • SDK layer: zero is passed straight through to LDConfig.startWait, so the LDClient constructor returns immediately. That is the "don't wait at all" behavior you'd expect.
  • Provider layer: initialize is invoked asynchronously by the OpenFeature API, so "no timeout" is not the application blocking forever — it means the provider does not fail initialization on a clock, and instead settles when the data source becomes valid or permanently fails. That is also the behavior on main today for every existing caller.

So the semantics follow the spec, but "waits indefinitely" is a misleading way to describe it, and if zero reads as "don't wait" to you it will read that way to users. Options, happy to take direction:

  1. Keep zero as the spec defines it and fix the wording to talk about not applying a timeout rather than waiting indefinitely.
  2. Make the no-timeout case a distinct value (null, or a negative duration) and let zero mean fail immediately if not already ready — this diverges from OFP 4.3.4.2, so it would want a spec change rather than just a provider change.

I'd lean towards 1 plus better docs, since the spec is cross-SDK, but you know the intent behind the wording better than I do. Which do you prefer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

So, rephrasing it, we are saying:
This method will return immediately when the timeout is 0.
The time for the open feature initialized event itself is unbounded. It will be emitted when the SDK initializes or fails to initialize. If a timeout is provided, then an event will be emitted when the timeout lapses?

Or is it distinct from that?

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Close, with one correction on the first line — it's the constructor, not initialize, that returns immediately.

With Duration.ZERO:

  • The Provider constructor returns immediately. Zero goes to LDConfig.startWait, and the SDK checks isZero()/isNegative() and skips its wait on the data system future entirely.
  • initialize is unbounded: it blocks on the data source status until VALID (emits PROVIDER_READY) or OFF (emits PROVIDER_ERROR and throws). Nothing is emitted on a clock. Whether that blocks your thread is the OpenFeature API's choice, not the provider's — setProvider runs initialize on a background thread, setProviderAndWait blocks.

With a positive duration, both layers get it: the SDK constructor blocks up to that long, and initialize additionally stops waiting when it lapses, sets provider state to ERROR, and throws — the OpenFeature SDK wraps that in a GeneralError and emits PROVIDER_ERROR. So yes, an event on timeout, but as a failure rather than a separate timeout signal.

One consequence worth a decision: the status listener stays registered after that throw, so if the data source becomes valid later, the provider still emits PROVIDER_READY even though initialization already failed. I think that's desirable — it's how the SDK recovers on its own — but it does mean the timeout bounds initialization, not the provider's lifetime. Say the word if you'd rather a lapsed timeout be terminal.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Following up on this after 095f4ea, since the behavior I described has changed: a positive duration is now spent once rather than twice. The SDK constructor blocks for up to startWait, and initialize then reports whatever the outcome already is instead of starting its own timed wait — so a two second start wait can no longer add up to four seconds of waiting. Zero still means no provider-applied timeout, per OFP 4.3.4.2, and initialize waits until the data source is valid or permanently fails.

The consequence I flagged above is unchanged: the status listener stays registered after a failed initialization, so a data source that becomes valid later still emits PROVIDER_READY. Still happy to make a lapsed start wait terminal instead if that's what you'd prefer.

devin-ai-integration Bot and others added 2 commits August 28, 2026 21:57
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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

❌ 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 70082f7. Configure here.

…rt wait

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

Provider initialization can block indefinitely

2 participants


Back | FazBrowse Home | New Git URL