Skip to content

Commit 737bd9d

Browse files
author
Bernd Warmuth
committed
feat: refactor GrpcConnector to use grpc builtin reconnection
Signed-off-by: Bernd Warmuth <[email protected]>
1 parent d8daa07 commit 737bd9d

File tree

18 files changed

+700
-1158
lines changed

18 files changed

+700
-1158
lines changed

providers/flagd/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,12 @@
150150
<version>1.20.4</version>
151151
<scope>test</scope>
152152
</dependency>
153-
153+
<dependency>
154+
<groupId>io.grpc</groupId>
155+
<artifactId>grpc-testing</artifactId>
156+
<version>1.69.0</version>
157+
<scope>test</scope>
158+
</dependency>
154159
</dependencies>
155160

156161
<build>

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public final class Config {
5757
public static final String LRU_CACHE = CacheType.LRU.getValue();
5858
static final String DEFAULT_CACHE = LRU_CACHE;
5959

60-
static final int DEFAULT_MAX_EVENT_STREAM_RETRIES = 7;
6160
static final int BASE_EVENT_STREAM_RETRY_BACKOFF_MS = 1000;
6261

6362
static String fallBackToEnvOrDefault(String key, String defaultValue) {

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,6 @@ public class FlagdOptions {
6969
private int maxCacheSize = fallBackToEnvOrDefault(Config.MAX_CACHE_SIZE_ENV_VAR_NAME,
7070
Config.DEFAULT_MAX_CACHE_SIZE);
7171

72-
/**
73-
* Max event stream connection retries.
74-
*/
75-
@Builder.Default
76-
private int maxEventStreamRetries = fallBackToEnvOrDefault(Config.MAX_EVENT_STREAM_RETRIES_ENV_VAR_NAME,
77-
Config.DEFAULT_MAX_EVENT_STREAM_RETRIES);
78-
7972
/**
8073
* Backoff interval in milliseconds.
8174
*/
@@ -102,11 +95,12 @@ public class FlagdOptions {
10295
Config.DEFAULT_STREAM_DEADLINE_MS);
10396

10497
/**
105-
* Amount of stream retry attempts before provider moves from STALE to ERROR
106-
* Defaults to 5
98+
* Grace time period in milliseconds before provider moves from STALE to ERROR.
99+
* Defaults to 50_000
107100
*/
108101
@Builder.Default
109-
private int streamRetryGracePeriod = fallBackToEnvOrDefault(Config.STREAM_RETRY_GRACE_PERIOD, Config.DEFAULT_STREAM_RETRY_GRACE_PERIOD);
102+
private int streamRetryGracePeriod = fallBackToEnvOrDefault(Config.STREAM_RETRY_GRACE_PERIOD,
103+
Config.DEFAULT_STREAM_RETRY_GRACE_PERIOD);
110104
/**
111105
* Selector to be used with flag sync gRPC contract.
112106
**/
@@ -116,7 +110,6 @@ public class FlagdOptions {
116110
/**
117111
* gRPC client KeepAlive in milliseconds. Disabled with 0.
118112
* Defaults to 0 (disabled).
119-
*
120113
**/
121114
@Builder.Default
122115
private long keepAlive = fallBackToEnvOrDefault(Config.KEEP_ALIVE_MS_ENV_VAR_NAME,

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

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package dev.openfeature.contrib.providers.flagd;
22

3-
import java.util.ArrayList;
4-
import java.util.Collections;
5-
import java.util.List;
6-
import java.util.function.Function;
7-
83
import dev.openfeature.contrib.providers.flagd.resolver.Resolver;
94
import dev.openfeature.contrib.providers.flagd.resolver.common.ConnectionEvent;
105
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcResolver;
@@ -22,16 +17,21 @@
2217
import dev.openfeature.sdk.Value;
2318
import lombok.extern.slf4j.Slf4j;
2419

20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.function.Function;
24+
2525
/**
2626
* OpenFeature provider for flagd.
2727
*/
2828
@Slf4j
29-
@SuppressWarnings({ "PMD.TooManyStaticImports", "checkstyle:NoFinalizer" })
29+
@SuppressWarnings({"PMD.TooManyStaticImports", "checkstyle:NoFinalizer"})
3030
public class FlagdProvider extends EventProvider {
3131
private Function<Structure, EvaluationContext> contextEnricher;
3232
private static final String FLAGD_PROVIDER = "flagd";
3333
private final Resolver flagResolver;
34-
private volatile boolean initialized = false;
34+
private volatile boolean isInitialized = false;
3535
private volatile boolean connected = false;
3636
private volatile Structure syncMetadata = new ImmutableStructure();
3737
private volatile EvaluationContext enrichedContext = new ImmutableContext();
@@ -62,7 +62,6 @@ public FlagdProvider(final FlagdOptions options) {
6262
case Config.RESOLVER_RPC:
6363
this.flagResolver = new GrpcResolver(options,
6464
new Cache(options.getCacheType(), options.getMaxCacheSize()),
65-
this::isConnected,
6665
this::onConnectionEvent);
6766
break;
6867
default:
@@ -80,17 +79,17 @@ public List<Hook> getProviderHooks() {
8079

8180
@Override
8281
public synchronized void initialize(EvaluationContext evaluationContext) throws Exception {
83-
if (this.initialized) {
82+
if (this.isInitialized) {
8483
return;
8584
}
8685

8786
this.flagResolver.init();
88-
this.initialized = true;
87+
this.isInitialized = true;
8988
}
9089

9190
@Override
9291
public synchronized void shutdown() {
93-
if (!this.initialized) {
92+
if (!this.isInitialized) {
9493
return;
9594
}
9695

@@ -99,7 +98,7 @@ public synchronized void shutdown() {
9998
} catch (Exception e) {
10099
log.error("Error during shutdown {}", FLAGD_PROVIDER, e);
101100
} finally {
102-
this.initialized = false;
101+
this.isInitialized = false;
103102
}
104103
}
105104

@@ -139,7 +138,7 @@ public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultVa
139138
* Set on initial connection and updated with every reconnection.
140139
* see:
141140
* https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.FlagSyncService.GetMetadata
142-
*
141+
*
143142
* @return Object map representing sync metadata
144143
*/
145144
protected Structure getSyncMetadata() {
@@ -148,6 +147,7 @@ protected Structure getSyncMetadata() {
148147

149148
/**
150149
* The updated context mixed into all evaluations based on the sync-metadata.
150+
*
151151
* @return context
152152
*/
153153
EvaluationContext getEnrichedContext() {
@@ -159,33 +159,41 @@ private boolean isConnected() {
159159
}
160160

161161
private void onConnectionEvent(ConnectionEvent connectionEvent) {
162-
boolean previous = connected;
163-
boolean current = connected = connectionEvent.isConnected();
162+
final boolean wasConnected = connected;
163+
final boolean isConnected = connected = connectionEvent.isConnected();
164+
164165
syncMetadata = connectionEvent.getSyncMetadata();
165166
enrichedContext = contextEnricher.apply(connectionEvent.getSyncMetadata());
166167

167-
// configuration changed
168-
if (initialized && previous && current) {
169-
log.debug("Configuration changed");
168+
if (!isInitialized) {
169+
return;
170+
}
171+
172+
if (!wasConnected && isConnected) {
170173
ProviderEventDetails details = ProviderEventDetails.builder()
171174
.flagsChanged(connectionEvent.getFlagsChanged())
172-
.message("configuration changed").build();
173-
this.emitProviderConfigurationChanged(details);
175+
.message("connected to flagd")
176+
.build();
177+
this.emitProviderReady(details);
174178
return;
175179
}
176-
// there was an error
177-
if (initialized && previous && !current) {
178-
log.debug("There has been an error");
179-
ProviderEventDetails details = ProviderEventDetails.builder().message("there has been an error").build();
180-
this.emitProviderError(details);
180+
181+
if (wasConnected && isConnected) {
182+
ProviderEventDetails details = ProviderEventDetails.builder()
183+
.flagsChanged(connectionEvent.getFlagsChanged())
184+
.message("configuration changed")
185+
.build();
186+
this.emitProviderConfigurationChanged(details);
181187
return;
182188
}
183-
// we recovered from an error
184-
if (initialized && !previous && current) {
185-
log.debug("Recovered from error");
186-
ProviderEventDetails details = ProviderEventDetails.builder().message("recovered from error").build();
187-
this.emitProviderReady(details);
188-
this.emitProviderConfigurationChanged(details);
189+
190+
if (connectionEvent.isStale()) {
191+
this.emitProviderStale(ProviderEventDetails.builder().message("there has been an error").build());
192+
} else {
193+
this.emitProviderError(ProviderEventDetails.builder().message("there has been an error").build());
189194
}
190195
}
191196
}
197+
198+
199+
Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,122 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.common;
22

3-
import java.util.Collections;
4-
import java.util.List;
5-
63
import dev.openfeature.sdk.ImmutableStructure;
74
import dev.openfeature.sdk.Structure;
8-
import lombok.AllArgsConstructor;
9-
import lombok.Getter;
5+
6+
import java.util.Collections;
7+
import java.util.List;
108

119
/**
12-
* Event payload for a
13-
* {@link dev.openfeature.contrib.providers.flagd.resolver.Resolver} connection
14-
* state change event.
10+
* Represents an event payload for a connection state change in a
11+
* {@link dev.openfeature.contrib.providers.flagd.resolver.Resolver}.
12+
* The event includes information about the connection status, any flags that have changed,
13+
* and metadata associated with the synchronization process.
1514
*/
16-
@AllArgsConstructor
1715
public class ConnectionEvent {
18-
@Getter
19-
private final boolean connected;
16+
17+
/**
18+
* The current state of the connection.
19+
*/
20+
private final ConnectionState connected;
21+
22+
/**
23+
* A list of flags that have changed due to this connection event.
24+
*/
2025
private final List<String> flagsChanged;
26+
27+
/**
28+
* Metadata associated with synchronization in this connection event.
29+
*/
2130
private final Structure syncMetadata;
2231

2332
/**
24-
* Construct a new ConnectionEvent.
25-
*
26-
* @param connected status of the connection
33+
* Constructs a new {@code ConnectionEvent} with the connection status only.
34+
*
35+
* @param connected {@code true} if the connection is established, otherwise {@code false}.
2736
*/
2837
public ConnectionEvent(boolean connected) {
38+
this(connected ? ConnectionState.CONNECTED : ConnectionState.DISCONNECTED, Collections.emptyList(),
39+
new ImmutableStructure());
40+
}
41+
42+
/**
43+
* Constructs a new {@code ConnectionEvent} with the specified connection state.
44+
*
45+
* @param connected the connection state indicating if the connection is established or not.
46+
*/
47+
public ConnectionEvent(ConnectionState connected) {
2948
this(connected, Collections.emptyList(), new ImmutableStructure());
3049
}
3150

3251
/**
33-
* Construct a new ConnectionEvent.
34-
*
35-
* @param connected status of the connection
36-
* @param flagsChanged list of flags changed
52+
* Constructs a new {@code ConnectionEvent} with the specified connection state and changed flags.
53+
*
54+
* @param connected the connection state indicating if the connection is established or not.
55+
* @param flagsChanged a list of flags that have changed due to this connection event.
3756
*/
38-
public ConnectionEvent(boolean connected, List<String> flagsChanged) {
57+
public ConnectionEvent(ConnectionState connected, List<String> flagsChanged) {
3958
this(connected, flagsChanged, new ImmutableStructure());
4059
}
4160

4261
/**
43-
* Construct a new ConnectionEvent.
44-
*
45-
* @param connected status of the connection
46-
* @param syncMetadata sync.getMetadata
62+
* Constructs a new {@code ConnectionEvent} with the specified connection state and synchronization metadata.
63+
*
64+
* @param connected the connection state indicating if the connection is established or not.
65+
* @param syncMetadata metadata related to the synchronization process of this event.
4766
*/
48-
public ConnectionEvent(boolean connected, Structure syncMetadata) {
67+
public ConnectionEvent(ConnectionState connected, Structure syncMetadata) {
4968
this(connected, Collections.emptyList(), new ImmutableStructure(syncMetadata.asMap()));
5069
}
5170

5271
/**
53-
* Get changed flags.
54-
*
55-
* @return an unmodifiable view of the changed flags
72+
* Constructs a new {@code ConnectionEvent} with the specified connection state, changed flags, and
73+
* synchronization metadata.
74+
*
75+
* @param connectionState the state of the connection.
76+
* @param flagsChanged a list of flags that have changed due to this connection event.
77+
* @param syncMetadata metadata related to the synchronization process of this event.
78+
*/
79+
public ConnectionEvent(ConnectionState connectionState, List<String> flagsChanged, Structure syncMetadata) {
80+
this.connected = connectionState;
81+
this.flagsChanged = flagsChanged != null ? flagsChanged : Collections.emptyList(); // Ensure non-null list
82+
this.syncMetadata = syncMetadata != null ? new ImmutableStructure(syncMetadata.asMap()) :
83+
new ImmutableStructure(); // Ensure valid syncMetadata
84+
}
85+
86+
/**
87+
* Retrieves an unmodifiable view of the list of changed flags.
88+
*
89+
* @return an unmodifiable list of changed flags.
5690
*/
5791
public List<String> getFlagsChanged() {
5892
return Collections.unmodifiableList(flagsChanged);
5993
}
6094

6195
/**
62-
* Get changed sync metadata represented as SDK structure type.
63-
*
64-
* @return an unmodifiable view of the sync metadata
96+
* Retrieves the synchronization metadata represented as an immutable SDK structure type.
97+
*
98+
* @return an immutable structure containing the synchronization metadata.
6599
*/
66100
public Structure getSyncMetadata() {
67101
return new ImmutableStructure(syncMetadata.asMap());
68102
}
103+
104+
/**
105+
* Indicates whether the current connection state is connected.
106+
*
107+
* @return {@code true} if connected, otherwise {@code false}.
108+
*/
109+
public boolean isConnected() {
110+
return this.connected == ConnectionState.CONNECTED;
111+
}
112+
113+
/**
114+
* Indicates
115+
* whether the current connection state is stale.
116+
*
117+
* @return {@code true} if stale, otherwise {@code false}.
118+
*/
119+
public boolean isStale() {
120+
return this.connected == ConnectionState.STALE;
121+
}
69122
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.openfeature.contrib.providers.flagd.resolver.common;
2+
3+
/**
4+
* Represents the possible states of a connection.
5+
*/
6+
public enum ConnectionState {
7+
8+
/**
9+
* The connection is active and functioning as expected.
10+
*/
11+
CONNECTED,
12+
13+
/**
14+
* The connection is not active and has been fully disconnected.
15+
*/
16+
DISCONNECTED,
17+
18+
/**
19+
* The connection is inactive or degraded but may still recover.
20+
*/
21+
STALE,
22+
23+
/**
24+
* The connection has encountered an error and cannot function correctly.
25+
*/
26+
ERROR,
27+
}

0 commit comments

Comments
 (0)