Skip to content

Commit b8eb026

Browse files
committed
feat: Reset the state on shutting down the flagd resolver (open-feature#410)
Signed-off-by: Sanket Mehta <[email protected]>
1 parent 2f7ed20 commit b8eb026

File tree

6 files changed

+82
-4
lines changed

6 files changed

+82
-4
lines changed

mvnw

100644100755
File mode changed.

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public synchronized void initialize(EvaluationContext evaluationContext) throws
8080

8181
@Override
8282
public synchronized void shutdown() {
83-
if (!initialized) {
83+
if (!this.initialized) {
8484
return;
8585
}
8686

@@ -162,6 +162,12 @@ private void handleStateTransition(ProviderState oldState, ProviderState newStat
162162
log.debug("Init completed");
163163
return;
164164
}
165+
// we got shutdown, not checking oldState as behavior remains the same for shutdown
166+
if (ProviderState.NOT_READY.equals(newState)) {
167+
// nothing to do
168+
log.debug("shutdown completed");
169+
return;
170+
}
165171
// configuration changed
166172
if (ProviderState.READY.equals(oldState) && ProviderState.READY.equals(newState)) {
167173
log.debug("Configuration changed");

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

+70
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;
@@ -16,22 +17,29 @@
1617

1718
import java.lang.reflect.Field;
1819
import java.util.ArrayList;
20+
import java.util.Arrays;
1921
import java.util.HashMap;
2022
import java.util.List;
2123
import java.util.Map;
2224
import java.util.concurrent.TimeUnit;
2325
import java.util.function.Supplier;
26+
import java.util.concurrent.LinkedBlockingQueue;
2427

2528
import org.junit.jupiter.api.BeforeAll;
2629
import org.junit.jupiter.api.Test;
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;
3543
import dev.openfeature.flagd.grpc.evaluation.ServiceGrpc;
3644
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveBooleanRequest;
3745
import dev.openfeature.flagd.grpc.evaluation.Evaluation.ResolveBooleanResponse;
@@ -824,6 +832,43 @@ void initializationAndShutdown() throws Exception{
824832
verify(resolverMock, times(1)).shutdown();
825833
}
826834

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

828873
// test helper
829874

@@ -863,4 +908,29 @@ private FlagdProvider createProvider(GrpcConnector grpc, Cache cache, Supplier<P
863908
return provider;
864909
}
865910

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

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)