Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
@Slf4j
class FeatureProviderStateManager implements EventProviderListener {
private final FeatureProvider delegate;
private final boolean delegateManagesState;
private final AtomicBoolean isInitialized = new AtomicBoolean();
private final AtomicReference<ProviderState> state = new AtomicReference<>(ProviderState.NOT_READY);

public FeatureProviderStateManager(FeatureProvider delegate) {
this.delegate = delegate;
this.delegateManagesState = delegate instanceof StateManagingProvider;
if (delegate instanceof EventProvider) {
((EventProvider) delegate).setEventProviderListener(this);
}
Expand All @@ -24,30 +26,41 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
}
try {
delegate.initialize(evaluationContext);
setState(ProviderState.READY);
if (!delegateManagesState) {
setState(ProviderState.READY);
}
} catch (OpenFeatureError openFeatureError) {
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
setState(ProviderState.FATAL);
} else {
setState(ProviderState.ERROR);
if (!delegateManagesState) {
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
setState(ProviderState.FATAL);
} else {
setState(ProviderState.ERROR);
}
}
isInitialized.set(false);
throw openFeatureError;
} catch (Exception e) {
setState(ProviderState.ERROR);
if (!delegateManagesState) {
setState(ProviderState.ERROR);
}
isInitialized.set(false);
throw e;
}
}

public void shutdown() {
delegate.shutdown();
setState(ProviderState.NOT_READY);
if (!delegateManagesState) {
setState(ProviderState.NOT_READY);
}
isInitialized.set(false);
}

@Override
public void onEmit(ProviderEvent event, ProviderEventDetails details) {
if (delegateManagesState) {
return;
}
if (ProviderEvent.PROVIDER_ERROR.equals(event)) {
if (details != null && details.getErrorCode() == ErrorCode.PROVIDER_FATAL) {
setState(ProviderState.FATAL);
Expand Down Expand Up @@ -75,13 +88,23 @@ private void setState(ProviderState state) {
}

public ProviderState getState() {
if (delegateManagesState) {
return delegate.getState();
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The getState() method on FeatureProvider is deprecated. To avoid potential compiler warnings and to make the code clearer, it's better to cast the delegate to StateManagingProvider before calling getState(). This makes it explicit that you are using the non-deprecated method from the StateManagingProvider interface.

Suggested change
return delegate.getState();
return ((StateManagingProvider) delegate).getState();

}
return state.get();
}

FeatureProvider getProvider() {
return delegate;
}

/**
* Returns true if the delegate provider manages its own state.
*/
boolean delegateManagesState() {
return delegateManagesState;
}

public boolean hasSameProvider(FeatureProvider featureProvider) {
return this.delegate.equals(featureProvider);
}
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/dev/openfeature/sdk/ProviderRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,30 @@ private void initializeProvider(
try {
if (ProviderState.NOT_READY.equals(newManager.getState())) {
newManager.initialize(openFeatureAPI.getEvaluationContext());
afterInit.accept(newManager.getProvider());
// State-managing providers emit their own events; skip SDK-side emission.
if (!newManager.delegateManagesState()) {
afterInit.accept(newManager.getProvider());
}
}
shutDownOld(oldManager, afterShutdown);
} catch (OpenFeatureError e) {
log.error(
"Exception when initializing feature provider {}",
newManager.getProvider().getClass().getName(),
e);
afterError.accept(newManager.getProvider(), e);
// State-managing providers emit their own events; skip SDK-side emission.
if (!newManager.delegateManagesState()) {
afterError.accept(newManager.getProvider(), e);
}
} catch (Exception e) {
log.error(
"Exception when initializing feature provider {}",
newManager.getProvider().getClass().getName(),
e);
afterError.accept(newManager.getProvider(), new GeneralError(e));
// State-managing providers emit their own events; skip SDK-side emission.
if (!newManager.delegateManagesState()) {
afterError.accept(newManager.getProvider(), new GeneralError(e));
}
}
}

Expand Down
28 changes: 28 additions & 0 deletions src/main/java/dev/openfeature/sdk/StateManagingProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.openfeature.sdk;

/**
* A provider that manages its own state. The SDK reads state from the provider
* rather than maintaining shadow state. Implementations MUST ensure that
* {@link #getState()} is safe for concurrent access and that state transitions
* and associated event emissions are atomic from the perspective of external observers.
*
* <p>Legacy providers that do not implement this interface continue to have their state
* managed by the SDK (deprecated behavior, to be removed in the next major version).</p>
*
* @see FeatureProvider
* @see EventProvider
*/
public interface StateManagingProvider extends FeatureProvider {

/**
* Returns the current state of this provider. Must reflect {@link ProviderState#NOT_READY}
* before {@link #initialize(EvaluationContext)} is called and after {@link #shutdown()} completes.
* Must reflect {@link ProviderState#READY} if {@link #initialize(EvaluationContext)} returns normally.
*
* <p>This method must be safe for concurrent access.</p>
*
* @return the current provider state
*/
@Override
ProviderState getState();
}
Loading