Skip to content

Commit 1111da6

Browse files
committed
feat: provider wrapper to support state race fix
1 parent 597501a commit 1111da6

2 files changed

Lines changed: 51 additions & 7 deletions

File tree

src/main/java/dev/openfeature/sdk/FeatureProviderStateManager.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
@Slf4j
99
class FeatureProviderStateManager implements EventProviderListener {
1010
private final FeatureProvider delegate;
11+
private final boolean delegateManagesState;
1112
private final AtomicBoolean isInitialized = new AtomicBoolean();
1213
private final AtomicReference<ProviderState> state = new AtomicReference<>(ProviderState.NOT_READY);
1314

1415
public FeatureProviderStateManager(FeatureProvider delegate) {
1516
this.delegate = delegate;
17+
this.delegateManagesState = delegate instanceof StateManagingProvider;
1618
if (delegate instanceof EventProvider) {
1719
((EventProvider) delegate).setEventProviderListener(this);
1820
}
@@ -24,30 +26,41 @@ public void initialize(EvaluationContext evaluationContext) throws Exception {
2426
}
2527
try {
2628
delegate.initialize(evaluationContext);
27-
setState(ProviderState.READY);
29+
if (!delegateManagesState) {
30+
setState(ProviderState.READY);
31+
}
2832
} catch (OpenFeatureError openFeatureError) {
29-
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
30-
setState(ProviderState.FATAL);
31-
} else {
32-
setState(ProviderState.ERROR);
33+
if (!delegateManagesState) {
34+
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
35+
setState(ProviderState.FATAL);
36+
} else {
37+
setState(ProviderState.ERROR);
38+
}
3339
}
3440
isInitialized.set(false);
3541
throw openFeatureError;
3642
} catch (Exception e) {
37-
setState(ProviderState.ERROR);
43+
if (!delegateManagesState) {
44+
setState(ProviderState.ERROR);
45+
}
3846
isInitialized.set(false);
3947
throw e;
4048
}
4149
}
4250

4351
public void shutdown() {
4452
delegate.shutdown();
45-
setState(ProviderState.NOT_READY);
53+
if (!delegateManagesState) {
54+
setState(ProviderState.NOT_READY);
55+
}
4656
isInitialized.set(false);
4757
}
4858

4959
@Override
5060
public void onEmit(ProviderEvent event, ProviderEventDetails details) {
61+
if (delegateManagesState) {
62+
return;
63+
}
5164
if (ProviderEvent.PROVIDER_ERROR.equals(event)) {
5265
if (details != null && details.getErrorCode() == ErrorCode.PROVIDER_FATAL) {
5366
setState(ProviderState.FATAL);
@@ -75,6 +88,9 @@ private void setState(ProviderState state) {
7588
}
7689

7790
public ProviderState getState() {
91+
if (delegateManagesState) {
92+
return delegate.getState();
93+
}
7894
return state.get();
7995
}
8096

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.openfeature.sdk;
2+
3+
/**
4+
* A provider that manages its own state. The SDK reads state from the provider
5+
* rather than maintaining shadow state. Implementations MUST ensure that
6+
* {@link #getState()} is safe for concurrent access and that state transitions
7+
* and associated event emissions are atomic from the perspective of external observers.
8+
*
9+
* <p>Legacy providers that do not implement this interface continue to have their state
10+
* managed by the SDK (deprecated behavior, to be removed in the next major version).</p>
11+
*
12+
* @see FeatureProvider
13+
* @see EventProvider
14+
*/
15+
public interface StateManagingProvider extends FeatureProvider {
16+
17+
/**
18+
* Returns the current state of this provider. Must reflect {@link ProviderState#NOT_READY}
19+
* before {@link #initialize(EvaluationContext)} is called and after {@link #shutdown()} completes.
20+
* Must reflect {@link ProviderState#READY} if {@link #initialize(EvaluationContext)} returns normally.
21+
*
22+
* <p>This method must be safe for concurrent access.</p>
23+
*
24+
* @return the current provider state
25+
*/
26+
@Override
27+
ProviderState getState();
28+
}

0 commit comments

Comments
 (0)