Skip to content

Commit 6afae99

Browse files
committed
fixup: fixing inprocess and eventing
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 82db146 commit 6afae99

21 files changed

+229
-254
lines changed

Diff for: providers/flagd/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
<groupId>org.slf4j</groupId>
160160
<artifactId>slf4j-simple</artifactId>
161161
<version>2.0.16</version>
162+
<scope>test</scope>
162163
</dependency>
163164
</dependencies>
164165

Diff for: providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/FlagdProvider.java

+101-38
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package dev.openfeature.contrib.providers.flagd;
22

33
import dev.openfeature.contrib.providers.flagd.resolver.Resolver;
4-
import dev.openfeature.contrib.providers.flagd.resolver.common.ConnectionEvent;
4+
import dev.openfeature.contrib.providers.flagd.resolver.common.FlagdProviderEvent;
5+
import dev.openfeature.contrib.providers.flagd.resolver.common.Util;
56
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcResolver;
67
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
78
import dev.openfeature.contrib.providers.flagd.resolver.process.InProcessResolver;
@@ -12,12 +13,17 @@
1213
import dev.openfeature.sdk.ImmutableStructure;
1314
import dev.openfeature.sdk.Metadata;
1415
import dev.openfeature.sdk.ProviderEvaluation;
16+
import dev.openfeature.sdk.ProviderEvent;
1517
import dev.openfeature.sdk.ProviderEventDetails;
1618
import dev.openfeature.sdk.Structure;
1719
import dev.openfeature.sdk.Value;
1820
import java.util.ArrayList;
1921
import java.util.Collections;
2022
import java.util.List;
23+
import java.util.concurrent.Executors;
24+
import java.util.concurrent.ScheduledExecutorService;
25+
import java.util.concurrent.ScheduledFuture;
26+
import java.util.concurrent.TimeUnit;
2127
import java.util.function.Function;
2228
import lombok.extern.slf4j.Slf4j;
2329

@@ -31,10 +37,29 @@ public class FlagdProvider extends EventProvider {
3137
private static final String FLAGD_PROVIDER = "flagd";
3238
private final Resolver flagResolver;
3339
private volatile boolean initialized = false;
34-
private volatile boolean connected = false;
3540
private volatile Structure syncMetadata = new ImmutableStructure();
3641
private volatile EvaluationContext enrichedContext = new ImmutableContext();
3742
private final List<Hook> hooks = new ArrayList<>();
43+
private volatile ProviderEvent previousEvent = null;
44+
45+
/**
46+
* An executor service responsible for scheduling reconnection attempts.
47+
*/
48+
private final ScheduledExecutorService reconnectExecutor;
49+
50+
/**
51+
* A scheduled task for managing reconnection attempts.
52+
*/
53+
private ScheduledFuture<?> reconnectTask;
54+
55+
/**
56+
* The grace period in milliseconds to wait for reconnection before emitting an error event.
57+
*/
58+
private final long gracePeriod;
59+
/**
60+
* The deadline in milliseconds for GRPC operations.
61+
*/
62+
private final long deadline;
3863

3964
protected final void finalize() {
4065
// DO NOT REMOVE, spotbugs: CT_CONSTRUCTOR_THROW
@@ -55,18 +80,21 @@ public FlagdProvider() {
5580
public FlagdProvider(final FlagdOptions options) {
5681
switch (options.getResolverType().asString()) {
5782
case Config.RESOLVER_IN_PROCESS:
58-
this.flagResolver = new InProcessResolver(options, this::isConnected, this::onConnectionEvent);
83+
this.flagResolver = new InProcessResolver(options, this::onProviderEvent);
5984
break;
6085
case Config.RESOLVER_RPC:
6186
this.flagResolver = new GrpcResolver(
62-
options, new Cache(options.getCacheType(), options.getMaxCacheSize()), this::onConnectionEvent);
87+
options, new Cache(options.getCacheType(), options.getMaxCacheSize()), this::onProviderEvent);
6388
break;
6489
default:
6590
throw new IllegalStateException(
6691
String.format("Requested unsupported resolver type of %s", options.getResolverType()));
6792
}
6893
hooks.add(new SyncMetadataHook(this::getEnrichedContext));
6994
contextEnricher = options.getContextEnricher();
95+
this.reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
96+
this.gracePeriod = options.getRetryGracePeriod();
97+
this.deadline = options.getDeadline();
7098
}
7199

72100
@Override
@@ -81,17 +109,22 @@ public synchronized void initialize(EvaluationContext evaluationContext) throws
81109
}
82110

83111
this.flagResolver.init();
84-
this.initialized = this.connected = true;
112+
// block till ready - this works with deadline fine for rpc, but with in_process we also need to take parsing into the equation
113+
Util.busyWaitAndCheck(this.deadline + 1000, () -> initialized);
114+
85115
}
86116

87117
@Override
88118
public synchronized void shutdown() {
89119
if (!this.initialized) {
90120
return;
91121
}
92-
93122
try {
94123
this.flagResolver.shutdown();
124+
if (reconnectExecutor != null) {
125+
reconnectExecutor.shutdownNow();
126+
reconnectExecutor.awaitTermination(deadline, TimeUnit.MILLISECONDS);
127+
}
95128
} catch (Exception e) {
96129
log.error("Error during shutdown {}", FLAGD_PROVIDER, e);
97130
} finally {
@@ -151,47 +184,77 @@ EvaluationContext getEnrichedContext() {
151184
return enrichedContext;
152185
}
153186

154-
private boolean isConnected() {
155-
return this.connected;
156-
}
187+
private void onProviderEvent(FlagdProviderEvent flagdProviderEvent) {
157188

158-
private void onConnectionEvent(ConnectionEvent connectionEvent) {
159-
final boolean wasConnected = connected;
160-
final boolean isConnected = connected = connectionEvent.isConnected();
189+
syncMetadata = flagdProviderEvent.getSyncMetadata();
190+
if (flagdProviderEvent.getSyncMetadata() != null) {
191+
enrichedContext = contextEnricher.apply(flagdProviderEvent.getSyncMetadata());
192+
}
161193

162-
syncMetadata = connectionEvent.getSyncMetadata();
163-
enrichedContext = contextEnricher.apply(connectionEvent.getSyncMetadata());
194+
switch (flagdProviderEvent.getEvent()) {
195+
case PROVIDER_CONFIGURATION_CHANGED:
196+
if (previousEvent == ProviderEvent.PROVIDER_READY) {
197+
this.emitProviderConfigurationChanged(
198+
ProviderEventDetails
199+
.builder()
200+
.flagsChanged(flagdProviderEvent.getFlagsChanged())
201+
.message("configuration changed")
202+
.build());
203+
break;
204+
}
205+
case PROVIDER_READY:
206+
onReady();
207+
previousEvent = ProviderEvent.PROVIDER_READY;
208+
break;
164209

165-
if (!initialized) {
166-
return;
210+
case PROVIDER_ERROR:
211+
if (previousEvent != ProviderEvent.PROVIDER_ERROR) {
212+
onError();
213+
}
214+
previousEvent = ProviderEvent.PROVIDER_ERROR;
215+
break;
167216
}
217+
}
168218

169-
if (!wasConnected && isConnected) {
170-
ProviderEventDetails details = ProviderEventDetails.builder()
171-
.flagsChanged(connectionEvent.getFlagsChanged())
172-
.message("connected to flagd")
173-
.build();
174-
this.emitProviderReady(details);
175-
return;
219+
private void onReady() {
220+
if(!initialized) {
221+
initialized = true;
222+
log.info("initialized FlagdProvider");
223+
}
224+
if (reconnectTask != null && !reconnectTask.isCancelled()) {
225+
reconnectTask.cancel(false);
226+
log.debug("Reconnection task cancelled as connection became READY.");
176227
}
228+
this.emitProviderReady(
229+
ProviderEventDetails
230+
.builder()
231+
.message("connected to flagd")
232+
.build());
233+
}
177234

178-
if (wasConnected && isConnected) {
179-
ProviderEventDetails details = ProviderEventDetails.builder()
180-
.flagsChanged(connectionEvent.getFlagsChanged())
181-
.message("configuration changed")
182-
.build();
183-
this.emitProviderConfigurationChanged(details);
184-
return;
235+
private void onError() {
236+
log.info("Connection lost. Emit STALE event...");
237+
log.debug("Waiting {}s for connection to become available...", gracePeriod);
238+
this.emitProviderStale(ProviderEventDetails.builder().message("there has been an error").build());
239+
240+
if (reconnectTask != null && !reconnectTask.isCancelled()) {
241+
reconnectTask.cancel(false);
185242
}
186243

187-
if (connectionEvent.isStale()) {
188-
this.emitProviderStale(ProviderEventDetails.builder()
189-
.message("there has been an error")
190-
.build());
191-
} else {
192-
this.emitProviderError(ProviderEventDetails.builder()
193-
.message("there has been an error")
194-
.build());
244+
if (!reconnectExecutor.isShutdown()) {
245+
reconnectTask = reconnectExecutor.schedule(
246+
() -> {
247+
log.debug(
248+
"Provider did not reconnect successfully within {}s. Emit ERROR event...", gracePeriod);
249+
flagResolver.onError();
250+
this.emitProviderError(ProviderEventDetails.builder()
251+
.message("there has been an error")
252+
.build());
253+
;
254+
},
255+
gracePeriod,
256+
TimeUnit.SECONDS);
195257
}
258+
196259
}
197260
}

Diff for: providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/Resolver.java

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public interface Resolver {
1010

1111
void shutdown() throws Exception;
1212

13+
default void onError() {}
14+
1315
ProviderEvaluation<Boolean> booleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx);
1416

1517
ProviderEvaluation<String> stringEvaluation(String key, String defaultValue, EvaluationContext ctx);

Diff for: providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/common/ChannelMonitor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public static void monitorChannelState(
3232
Runnable onConnectionLost) {
3333
channel.notifyWhenStateChanged(expectedState, () -> {
3434
ConnectivityState currentState = channel.getState(true);
35-
log.info("Channel state changed to: {}", currentState);
35+
//log.info("Channel state changed to: {}", currentState);
3636
if (currentState == ConnectivityState.READY) {
3737
if (onConnectionReady != null) {
3838
onConnectionReady.run();

Diff for: providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/common/ConnectionState.java

-27
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.common;
22

33
import dev.openfeature.sdk.ImmutableStructure;
4+
import dev.openfeature.sdk.ProviderEvent;
45
import dev.openfeature.sdk.Structure;
56
import lombok.Getter;
67
import java.util.Collections;
@@ -12,13 +13,13 @@
1213
* The event includes information about the connection status, any flags that have changed,
1314
* and metadata associated with the synchronization process.
1415
*/
15-
public class ConnectionEvent {
16+
public class FlagdProviderEvent {
1617

1718
/**
1819
* The current state of the connection.
1920
*/
2021
@Getter
21-
private final ConnectionState connected;
22+
private final ProviderEvent event;
2223

2324
/**
2425
* A list of flags that have changed due to this connection event.
@@ -30,57 +31,45 @@ public class ConnectionEvent {
3031
*/
3132
private final Structure syncMetadata;
3233

33-
/**
34-
* Constructs a new {@code ConnectionEvent} with the connection status only.
35-
*
36-
* @param connected {@code true} if the connection is established, otherwise {@code false}.
37-
*/
38-
public ConnectionEvent(boolean connected) {
39-
this(
40-
connected ? ConnectionState.CONNECTED : ConnectionState.DISCONNECTED,
41-
Collections.emptyList(),
42-
new ImmutableStructure());
43-
}
44-
4534
/**
4635
* Constructs a new {@code ConnectionEvent} with the specified connection state.
4736
*
48-
* @param connected the connection state indicating if the connection is established or not.
37+
* @param event the event indicating the provider state.
4938
*/
50-
public ConnectionEvent(ConnectionState connected) {
51-
this(connected, Collections.emptyList(), new ImmutableStructure());
39+
public FlagdProviderEvent(ProviderEvent event) {
40+
this(event, Collections.emptyList(), new ImmutableStructure());
5241
}
5342

5443
/**
5544
* Constructs a new {@code ConnectionEvent} with the specified connection state and changed flags.
5645
*
57-
* @param connected the connection state indicating if the connection is established or not.
46+
* @param event the event indicating the provider state.
5847
* @param flagsChanged a list of flags that have changed due to this connection event.
5948
*/
60-
public ConnectionEvent(ConnectionState connected, List<String> flagsChanged) {
61-
this(connected, flagsChanged, new ImmutableStructure());
49+
public FlagdProviderEvent(ProviderEvent event, List<String> flagsChanged) {
50+
this(event, flagsChanged, new ImmutableStructure());
6251
}
6352

6453
/**
6554
* Constructs a new {@code ConnectionEvent} with the specified connection state and synchronization metadata.
6655
*
67-
* @param connected the connection state indicating if the connection is established or not.
56+
* @param event the event indicating the provider state.
6857
* @param syncMetadata metadata related to the synchronization process of this event.
6958
*/
70-
public ConnectionEvent(ConnectionState connected, Structure syncMetadata) {
71-
this(connected, Collections.emptyList(), new ImmutableStructure(syncMetadata.asMap()));
59+
public FlagdProviderEvent(ProviderEvent event, Structure syncMetadata) {
60+
this(event, Collections.emptyList(), new ImmutableStructure(syncMetadata.asMap()));
7261
}
7362

7463
/**
7564
* Constructs a new {@code ConnectionEvent} with the specified connection state, changed flags, and
7665
* synchronization metadata.
7766
*
78-
* @param connectionState the state of the connection.
67+
* @param event the event.
7968
* @param flagsChanged a list of flags that have changed due to this connection event.
8069
* @param syncMetadata metadata related to the synchronization process of this event.
8170
*/
82-
public ConnectionEvent(ConnectionState connectionState, List<String> flagsChanged, Structure syncMetadata) {
83-
this.connected = connectionState;
71+
public FlagdProviderEvent(ProviderEvent event, List<String> flagsChanged, Structure syncMetadata) {
72+
this.event = event;
8473
this.flagsChanged = flagsChanged != null ? flagsChanged : Collections.emptyList(); // Ensure non-null list
8574
this.syncMetadata = syncMetadata != null
8675
? new ImmutableStructure(syncMetadata.asMap())
@@ -105,32 +94,7 @@ public Structure getSyncMetadata() {
10594
return new ImmutableStructure(syncMetadata.asMap());
10695
}
10796

108-
/**
109-
* Indicates whether the current connection state is connected.
110-
*
111-
* @return {@code true} if connected, otherwise {@code false}.
112-
*/
113-
public boolean isConnected() {
114-
return this.connected == ConnectionState.CONNECTED;
115-
}
116-
117-
/**
118-
* Indicates
119-
* whether the current connection state is disconnected.
120-
*
121-
* @return {@code true} if disconnected, otherwise {@code false}.
122-
*/
12397
public boolean isDisconnected() {
124-
return this.connected == ConnectionState.DISCONNECTED;
98+
return event == ProviderEvent.PROVIDER_ERROR || event == ProviderEvent.PROVIDER_STALE;
12599
}
126-
/**
127-
* Indicates
128-
* whether the current connection state is stale.
129-
*
130-
* @return {@code true} if stale, otherwise {@code false}.
131-
*/
132-
public boolean isStale() {
133-
return this.connected == ConnectionState.STALE;
134-
}
135-
136100
}

0 commit comments

Comments
 (0)