Skip to content

Commit 8bdb31f

Browse files
committed
feat: Reset the state on shutting down the flagd resolver (open-feature#410)
1 parent 150cef7 commit 8bdb31f

File tree

6 files changed

+94
-4
lines changed

6 files changed

+94
-4
lines changed

mvnw

100644100755
File mode changed.

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import dev.openfeature.sdk.Value;
1515
import lombok.extern.slf4j.Slf4j;
1616

17+
import java.util.List;
1718
import java.util.concurrent.locks.Lock;
1819
import java.util.concurrent.locks.ReadWriteLock;
1920
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -80,7 +81,7 @@ public synchronized void initialize(EvaluationContext evaluationContext) throws
8081

8182
@Override
8283
public synchronized void shutdown() {
83-
if (!initialized) {
84+
if (!this.initialized) {
8485
return;
8586
}
8687

@@ -162,6 +163,12 @@ private void handleStateTransition(ProviderState oldState, ProviderState newStat
162163
log.debug("Init completed");
163164
return;
164165
}
166+
// we got shutdown, not checking oldState as behavior remains the same for shutdown
167+
if (ProviderState.NOT_READY.equals(newState)) {
168+
// nothing to do
169+
log.debug("shutdown completed");
170+
return;
171+
}
165172
// configuration changed
166173
if (ProviderState.READY.equals(oldState) && ProviderState.READY.equals(newState)) {
167174
log.debug("Configuration changed");
@@ -183,5 +190,12 @@ private void handleStateTransition(ProviderState oldState, ProviderState newStat
183190
this.emitProviderReady(details);
184191
this.emitProviderConfigurationChanged(details);
185192
}
193+
// we shutdown from an error
194+
if (ProviderState.ERROR.equals(oldState) && ProviderState.NOT_READY.equals(newState)) {
195+
log.debug("Shutdown from error");
196+
ProviderEventDetails details = ProviderEventDetails.builder().message("shutdown from error").build();
197+
this.emitProviderReady(details);
198+
this.emitProviderConfigurationChanged(details);
199+
}
186200
}
187201
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcConnector.java

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public void shutdown() throws Exception {
100100
this.channel.awaitTermination(this.deadline, TimeUnit.MILLISECONDS);
101101
log.warn(String.format("Unable to shut down channel by %d deadline", this.deadline));
102102
}
103+
this.stateConsumer.accept(ProviderState.NOT_READY);
103104
}
104105
}
105106

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public void init() throws Exception {
100100
public void shutdown() throws InterruptedException {
101101
flagStore.shutdown();
102102
this.connected.set(false);
103+
stateConsumer.accept(ProviderState.NOT_READY);
103104
}
104105

105106
/**

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/FlagdProviderTest.java

+74
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static org.mockito.ArgumentMatchers.any;
99
import static org.mockito.ArgumentMatchers.anyLong;
1010
import static org.mockito.ArgumentMatchers.argThat;
11+
import static org.mockito.Mockito.doAnswer;
1112
import static org.mockito.Mockito.mock;
1213
import static org.mockito.Mockito.mockStatic;
1314
import static org.mockito.Mockito.times;
@@ -21,18 +22,27 @@
2122
import java.util.Map;
2223
import java.util.concurrent.TimeUnit;
2324
import java.util.function.Supplier;
25+
import java.util.concurrent.LinkedBlockingQueue;
2426

2527
import org.junit.jupiter.api.BeforeAll;
2628
import org.junit.jupiter.api.Test;
29+
import org.mockito.ArgumentMatchers;
2730
import org.mockito.MockedStatic;
31+
import org.mockito.Mockito;
2832

2933
import com.google.protobuf.Struct;
3034

3135
import dev.openfeature.contrib.providers.flagd.resolver.Resolver;
3236
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcConnector;
3337
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcResolver;
3438
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
39+
import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessResolver;
40+
import dev.openfeature.contrib.providers.flagd.resolver.process.MockStorage;
41+
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
42+
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.StorageState;
43+
import dev.openfeature.flagd.grpc.Schema.EventStreamResponse;
3544
import dev.openfeature.flagd.grpc.evaluation.ServiceGrpc;
45+
import dev.openfeature.flagd.grpc.evaluation.Evaluation.EventStreamRequest;
3646
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveBooleanRequest;
3747
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveBooleanResponse;
3848
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveFloatResponse;
@@ -52,9 +62,11 @@
5262
import dev.openfeature.sdk.Reason;
5363
import dev.openfeature.sdk.Structure;
5464
import dev.openfeature.sdk.Value;
65+
import io.cucumber.core.stepexpression.ArgumentMatcher;
5566
import io.cucumber.java.AfterAll;
5667
import io.grpc.Channel;
5768
import io.grpc.Deadline;
69+
import io.grpc.stub.StreamObserver;
5870

5971
class FlagdProviderTest {
6072
private static final String FLAG_KEY = "some-key";
@@ -824,6 +836,43 @@ void initializationAndShutdown() throws Exception{
824836
verify(resolverMock, times(1)).shutdown();
825837
}
826838

839+
@Test
840+
void test_state_on_grpc_resolver_shutdown() throws Exception {
841+
// setup mock provider
842+
final FlagdProvider grpcProvider = Mockito.spy(FlagdProvider.class);
843+
try {
844+
doAnswer(invocation -> {
845+
final Field stateField = FlagdProvider.class.getDeclaredField("state");
846+
stateField.setAccessible(true);
847+
stateField.set(grpcProvider, ProviderState.READY);
848+
849+
final Field intializedField = FlagdProvider.class.getDeclaredField("initialized");
850+
intializedField.setAccessible(true);
851+
intializedField.set(grpcProvider, true);
852+
853+
return null;
854+
}).when(grpcProvider).initialize(any());
855+
} catch (Exception e) {
856+
throw new RuntimeException(e);
857+
}
858+
859+
grpcProvider.initialize(new ImmutableContext());
860+
assertEquals(ProviderState.READY, grpcProvider.getState());
861+
grpcProvider.shutdown();
862+
assertEquals(ProviderState.NOT_READY, grpcProvider.getState());
863+
}
864+
865+
@Test
866+
void test_state_on_in_process_resolver_shutdown() throws Exception {
867+
// setup mock in-process provider
868+
FlagdProvider inProcessProvider = createInProcessProvider();
869+
870+
inProcessProvider.initialize(new ImmutableContext());
871+
assertEquals(ProviderState.READY, inProcessProvider.getState());
872+
inProcessProvider.shutdown();
873+
assertEquals(ProviderState.NOT_READY, inProcessProvider.getState());
874+
}
875+
827876

828877
// test helper
829878

@@ -863,4 +912,29 @@ private FlagdProvider createProvider(GrpcConnector grpc, Cache cache, Supplier<P
863912
return provider;
864913
}
865914

915+
// Create an in process provider
916+
private FlagdProvider createInProcessProvider() {
917+
918+
final FlagdOptions flagdOptions = FlagdOptions.builder()
919+
.resolverType(Config.Resolver.IN_PROCESS)
920+
.deadline(1000)
921+
.build();
922+
final FlagdProvider provider = new FlagdProvider(flagdOptions);
923+
final MockStorage mockStorage = new MockStorage(new HashMap<String, FeatureFlag>(), new LinkedBlockingQueue<StorageState>(List.of(StorageState.OK)));
924+
925+
try {
926+
final Field flagResolver = FlagdProvider.class.getDeclaredField("flagResolver");
927+
flagResolver.setAccessible(true);
928+
final Resolver resolver = (Resolver) flagResolver.get(provider);
929+
930+
final Field flagStore = InProcessResolver.class.getDeclaredField("flagStore");
931+
flagStore.setAccessible(true);
932+
flagStore.set(resolver, mockStorage);
933+
} catch (NoSuchFieldException | IllegalAccessException e) {
934+
throw new RuntimeException(e);
935+
}
936+
937+
return provider;
938+
}
939+
866940
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockStorage.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
import java.util.Map;
99
import java.util.concurrent.BlockingQueue;
1010

11-
class MockStorage implements Storage {
11+
public class MockStorage implements Storage {
1212

1313
private final Map<String, FeatureFlag> mockFlags;
1414
private final BlockingQueue<StorageState> mockQueue;
1515

16-
MockStorage(Map<String, FeatureFlag> mockFlags, BlockingQueue<StorageState> mockQueue) {
16+
public MockStorage(Map<String, FeatureFlag> mockFlags, BlockingQueue<StorageState> mockQueue) {
1717
this.mockFlags = mockFlags;
1818
this.mockQueue = mockQueue;
1919
}
2020

21-
MockStorage(Map<String, FeatureFlag> flagMap) {
21+
public MockStorage(Map<String, FeatureFlag> flagMap) {
2222
this.mockFlags = flagMap;
2323
this.mockQueue = null;
2424
}

0 commit comments

Comments
 (0)