feat!: Add SMP + Legacy Provider adaptor#241
Open
maxnrp wants to merge 14 commits into
Open
Conversation
2bf7987 to
7965fd2
Compare
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces the StateManagingProvider interface, which allows providers to manage their own lifecycle status via a StateFlow. The OpenFeatureAPI has been refactored to derive the SDK's overall status from the active provider, and a LegacyFeatureProviderAdapter was added to wrap existing FeatureProvider implementations, which are now deprecated. Feedback was provided regarding a redundant status check in the legacy adapter's initialization logic.
7965fd2 to
2290aff
Compare
Member
|
Related spec PR: open-feature/spec#367 |
2d2e563 to
9bc01c7
Compare
482c01f to
135d7b3
Compare
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Co-authored-by: Oleksii Shmalko <oleksii.shmalko@datadoghq.com> Signed-off-by: Max <73404116+maxnrp@users.noreply.github.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Tyler Potter <tyler.john.potter@gmail.com>
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
Signed-off-by: Max <73404116+maxnrp@users.noreply.github.com>
fe104e3 to
996c782
Compare
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
e2b6b8d to
d08f327
Compare
dd-oleksii
reviewed
May 6, 2026
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
a818129 to
c63c8e5
Compare
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
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Address the race condition in the current SDK implementation, where lifecycle state is split between the provider and the API, causing undefined event ordering in concurrent scenarios.
This is achieved by narrowing event emission to a single source of truth — the provider itself — via the
status: StateFlow<OpenFeatureStatus>property onStateManagingProvider, eliminating the window betweeninitialize()returning and the API updating its internal state. State transitions are the authoritative origin of events, which are emitted in response to each lifecycle stage change.Introduce a small helper for
StateManagingProviderimplementations, theProviderStatusTracker: one place to driveOpenFeatureStatusand the provider event stream with the same event → status rules as the rest of the SDK.Motivation
open-feature/spec#365 — Provider lifecycle race condition in multi-threaded SDKs
The issue above describes how split ownership of state and events between the API and the provider can produce undefined ordering under concurrency (e.g. API publishing “ready” while the provider emits something else from background work when it actually finishes initializing).
This change aligns the Kotlin SDK with the direction there: the provider is the party that can own a single, authoritative
OpenFeatureStatusstream. The API forwards it instead of racing to update the same conceptual state after initialize for implementations. That does not rewrite the whole spec, but it reduces split ownership for the new path while preserving the old behavior for legacy providers.Providers that only implement
FeatureProvider(and notStateManagingProvider) are automatically wrapped byLegacyFeatureProviderAdapterat registration time. The adapter derives status from the provider'sobserve()events — preserving all existing behavior including status buffering, Ready/Error handling aroundinitialize()andonContextSet(), and event mirroring — while making the provider fullyStateManagingProvider-compliant internally. This wrapping is transparent:getProvider()always returns the original provider instance.Changes
StateManagingProvideras an extension ofFeatureProvider, exposing status:StateFlow<OpenFeatureStatus>as the single source of truth for lifecycle state.LegacyFeatureProviderAdapter, an internal adapter that wraps plainFeatureProviderimplementations, deriving their status fromobserve()events and preserving legacy lifecycle semantics.OpenFeatureAPInow:Delegates
getStatus()andstatusFlowdirectly to the active provider'sstatus: StateFlow.Normalizes all registered providers to
StateManagingProviderat registration time — either directly if already implementing it, or viaLegacyFeatureProviderAdapter.Updates
setProviderandsetProviderAndWaitto suspend until the provider emits a state other thanNotReadyafterinitialize()completes.getProvider() unwraps LegacyFeatureProviderAdapter to always return the original registered provider.
NoOpProviderandMultiProviderimplementStateManagingProvider.ProviderStatusTrackerupdates derived StateFlow via toOpenFeatureStatus() and broadcasts with tryEmit on a MutableSharedFlow.Tests
StateManagingProviderStatusTests — Legacy init, observe mirrored into SDK status via LegacyFeatureProviderAdapter, onContextSet, getStatus from provider status, provider replacement/shutdown.
LegacyFeatureProviderStatusTests — Legacy init, observe mirrored into SDK
status,onContextSet,getStatusfrom SDK buffer, provider replacement/shutdown.StatusTests — End-to-end transitions, clear/shutdown, concurrent context updates with a state-managing slow provider, provider replacement (including async
setProvider).MultiProviderTests — Existing multi-provider behavior (strategies, lifecycle, track, error aggregation, init with long-running flows).
ProviderStatusTrackerTest — send → status mapping, replay + subsequent sends, not-ready/timeout, two collectors, ProviderConfigurationChanged not changing status, error details preserved under replay, etc.
Together, these unite legacy and state-managing behavior and cover init, status, and context and shutdown, under concurrency where relevant.
Breaking changes vs legacy compliance
StateManagingProvider(standard for custom providers)LegacyFeatureProviderAdapter(internal — not part of public API)StateManagingProviderDependency
This change requires a follow-up merge in: open-feature/kotlin-sdk-contrib