Skip to content

Commit d66adc9

Browse files
warbertoddbaert
andauthored
feat!: Use grpc intern reconnections for rpc event stream (#1112)
Signed-off-by: Bernd Warmuth <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent ee91441 commit d66adc9

File tree

19 files changed

+924
-1126
lines changed

19 files changed

+924
-1126
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public final class Config {
1515

1616
static final int DEFAULT_DEADLINE = 500;
1717
static final int DEFAULT_STREAM_DEADLINE_MS = 10 * 60 * 1000;
18+
static final int DEFAULT_STREAM_RETRY_GRACE_PERIOD = 5;
1819
static final int DEFAULT_MAX_CACHE_SIZE = 1000;
1920
static final long DEFAULT_KEEP_ALIVE = 0;
2021

@@ -35,6 +36,7 @@ public final class Config {
3536
static final String KEEP_ALIVE_MS_ENV_VAR_NAME_OLD = "FLAGD_KEEP_ALIVE_TIME";
3637
static final String KEEP_ALIVE_MS_ENV_VAR_NAME = "FLAGD_KEEP_ALIVE_TIME_MS";
3738
static final String TARGET_URI_ENV_VAR_NAME = "FLAGD_TARGET_URI";
39+
static final String STREAM_RETRY_GRACE_PERIOD = "FLAGD_RETRY_GRACE_PERIOD";
3840

3941
static final String RESOLVER_RPC = "rpc";
4042
static final String RESOLVER_IN_PROCESS = "in-process";
@@ -52,7 +54,6 @@ public final class Config {
5254
public static final String LRU_CACHE = CacheType.LRU.getValue();
5355
static final String DEFAULT_CACHE = LRU_CACHE;
5456

55-
static final int DEFAULT_MAX_EVENT_STREAM_RETRIES = 5;
5657
static final int BASE_EVENT_STREAM_RETRY_BACKOFF_MS = 1000;
5758

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

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

+74-38
Original file line numberDiff line numberDiff line change
@@ -13,114 +13,147 @@
1313
import lombok.Builder;
1414
import lombok.Getter;
1515

16-
/** FlagdOptions is a builder to build flagd provider options. */
16+
/**
17+
* FlagdOptions is a builder to build flagd provider options.
18+
*/
1719
@Builder
1820
@Getter
1921
@SuppressWarnings("PMD.TooManyStaticImports")
2022
public class FlagdOptions {
2123

22-
/** flagd resolving type. */
24+
/**
25+
* flagd resolving type.
26+
*/
2327
private Config.EvaluatorType resolverType;
2428

25-
/** flagd connection host. */
29+
/**
30+
* flagd connection host.
31+
*/
2632
@Builder.Default
2733
private String host = fallBackToEnvOrDefault(Config.HOST_ENV_VAR_NAME, Config.DEFAULT_HOST);
2834

29-
/** flagd connection port. */
35+
/**
36+
* flagd connection port.
37+
*/
3038
private int port;
3139

32-
/** Use TLS connectivity. */
40+
/**
41+
* Use TLS connectivity.
42+
*/
3343
@Builder.Default
3444
private boolean tls = Boolean.parseBoolean(fallBackToEnvOrDefault(Config.TLS_ENV_VAR_NAME, Config.DEFAULT_TLS));
3545

36-
/** TLS certificate overriding if TLS connectivity is used. */
46+
/**
47+
* TLS certificate overriding if TLS connectivity is used.
48+
*/
3749
@Builder.Default
3850
private String certPath = fallBackToEnvOrDefault(Config.SERVER_CERT_PATH_ENV_VAR_NAME, null);
3951

40-
/** Unix socket path to flagd. */
52+
/**
53+
* Unix socket path to flagd.
54+
*/
4155
@Builder.Default
4256
private String socketPath = fallBackToEnvOrDefault(Config.SOCKET_PATH_ENV_VAR_NAME, null);
4357

44-
/** Cache type to use. Supports - lru, disabled. */
58+
/**
59+
* Cache type to use. Supports - lru, disabled.
60+
*/
4561
@Builder.Default
4662
private String cacheType = fallBackToEnvOrDefault(Config.CACHE_ENV_VAR_NAME, Config.DEFAULT_CACHE);
4763

48-
/** Max cache size. */
64+
/**
65+
* Max cache size.
66+
*/
4967
@Builder.Default
5068
private int maxCacheSize =
5169
fallBackToEnvOrDefault(Config.MAX_CACHE_SIZE_ENV_VAR_NAME, Config.DEFAULT_MAX_CACHE_SIZE);
5270

53-
/** Max event stream connection retries. */
54-
@Builder.Default
55-
private int maxEventStreamRetries = fallBackToEnvOrDefault(
56-
Config.MAX_EVENT_STREAM_RETRIES_ENV_VAR_NAME, Config.DEFAULT_MAX_EVENT_STREAM_RETRIES);
57-
58-
/** Backoff interval in milliseconds. */
71+
/**
72+
* Backoff interval in milliseconds.
73+
*/
5974
@Builder.Default
6075
private int retryBackoffMs = fallBackToEnvOrDefault(
6176
Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS_ENV_VAR_NAME, Config.BASE_EVENT_STREAM_RETRY_BACKOFF_MS);
6277

6378
/**
64-
* Connection deadline in milliseconds. For RPC resolving, this is the deadline to connect to
65-
* flagd for flag evaluation. For in-process resolving, this is the deadline for sync stream
66-
* termination.
79+
* Connection deadline in milliseconds.
80+
* For RPC resolving, this is the deadline to connect to flagd for flag
81+
* evaluation.
82+
* For in-process resolving, this is the deadline for sync stream termination.
6783
*/
6884
@Builder.Default
6985
private int deadline = fallBackToEnvOrDefault(Config.DEADLINE_MS_ENV_VAR_NAME, Config.DEFAULT_DEADLINE);
7086

7187
/**
72-
* Streaming connection deadline in milliseconds. Set to 0 to disable the deadline. Defaults to
73-
* 600000 (10 minutes); recommended to prevent infrastructure from killing idle connections.
88+
* Streaming connection deadline in milliseconds.
89+
* Set to 0 to disable the deadline.
90+
* Defaults to 600000 (10 minutes); recommended to prevent infrastructure from killing idle connections.
7491
*/
7592
@Builder.Default
7693
private int streamDeadlineMs =
7794
fallBackToEnvOrDefault(Config.STREAM_DEADLINE_MS_ENV_VAR_NAME, Config.DEFAULT_STREAM_DEADLINE_MS);
7895

79-
/** Selector to be used with flag sync gRPC contract. */
96+
/**
97+
* Grace time period in seconds before provider moves from STALE to ERROR.
98+
* Defaults to 5
99+
*/
100+
@Builder.Default
101+
private int retryGracePeriod =
102+
fallBackToEnvOrDefault(Config.STREAM_RETRY_GRACE_PERIOD, Config.DEFAULT_STREAM_RETRY_GRACE_PERIOD);
103+
/**
104+
* Selector to be used with flag sync gRPC contract.
105+
**/
80106
@Builder.Default
81107
private String selector = fallBackToEnvOrDefault(Config.SOURCE_SELECTOR_ENV_VAR_NAME, null);
82108

83-
/** gRPC client KeepAlive in milliseconds. Disabled with 0. Defaults to 0 (disabled). */
109+
/**
110+
* gRPC client KeepAlive in milliseconds. Disabled with 0.
111+
* Defaults to 0 (disabled).
112+
**/
84113
@Builder.Default
85114
private long keepAlive = fallBackToEnvOrDefault(
86115
Config.KEEP_ALIVE_MS_ENV_VAR_NAME,
87116
fallBackToEnvOrDefault(Config.KEEP_ALIVE_MS_ENV_VAR_NAME_OLD, Config.DEFAULT_KEEP_ALIVE));
88117

89118
/**
90-
* File source of flags to be used by offline mode. Setting this enables the offline mode of the
91-
* in-process provider.
119+
* File source of flags to be used by offline mode.
120+
* Setting this enables the offline mode of the in-process provider.
92121
*/
93122
@Builder.Default
94123
private String offlineFlagSourcePath = fallBackToEnvOrDefault(Config.OFFLINE_SOURCE_PATH, null);
95124

96125
/**
97126
* gRPC custom target string.
98127
*
99-
* <p>Setting this will allow user to use custom gRPC name resolver at present we are supporting
100-
* all core resolver along with a custom resolver for envoy proxy resolution. For more visit
101-
* (https://grpc.io/docs/guides/custom-name-resolution/)
128+
* <p>Setting this will allow user to use custom gRPC name resolver at present
129+
* we are supporting all core resolver along with a custom resolver for envoy proxy
130+
* resolution. For more visit (https://grpc.io/docs/guides/custom-name-resolution/)
102131
*/
103132
@Builder.Default
104133
private String targetUri = fallBackToEnvOrDefault(Config.TARGET_URI_ENV_VAR_NAME, null);
105134

106135
/**
107-
* Function providing an EvaluationContext to mix into every evaluations. The sync-metadata
108-
* response
136+
* Function providing an EvaluationContext to mix into every evaluations.
137+
* The sync-metadata response
109138
* (https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.GetMetadataResponse),
110-
* represented as a {@link dev.openfeature.sdk.Structure}, is passed as an argument. This function
111-
* runs every time the provider (re)connects, and its result is cached and used in every
112-
* evaluation. By default, the entire sync response (converted to a Structure) is used.
139+
* represented as a {@link dev.openfeature.sdk.Structure}, is passed as an
140+
* argument.
141+
* This function runs every time the provider (re)connects, and its result is cached and used in every evaluation.
142+
* By default, the entire sync response (converted to a Structure) is used.
113143
*/
114144
@Builder.Default
115145
private Function<Structure, EvaluationContext> contextEnricher =
116146
(syncMetadata) -> new ImmutableContext(syncMetadata.asMap());
117147

118-
/** Inject a Custom Connector for fetching flags. */
148+
/**
149+
* Inject a Custom Connector for fetching flags.
150+
*/
119151
private Connector customConnector;
120152

121153
/**
122-
* Inject OpenTelemetry for the library runtime. Providing sdk will initiate distributed tracing
123-
* for flagd grpc connectivity.
154+
* Inject OpenTelemetry for the library runtime. Providing sdk will initiate
155+
* distributed tracing for flagd grpc
156+
* connectivity.
124157
*/
125158
private OpenTelemetry openTelemetry;
126159

@@ -139,11 +172,14 @@ public FlagdOptions build() {
139172
};
140173
}
141174

142-
/** Overload default lombok builder. */
175+
/**
176+
* Overload default lombok builder.
177+
*/
143178
public static class FlagdOptionsBuilder {
144179
/**
145-
* Enable OpenTelemetry instance extraction from GlobalOpenTelemetry. Note that, this is only
146-
* useful if global configurations are registered.
180+
* Enable OpenTelemetry instance extraction from GlobalOpenTelemetry. Note that,
181+
* this is only useful if global
182+
* configurations are registered.
147183
*/
148184
public FlagdOptionsBuilder withGlobalTelemetry(final boolean b) {
149185
if (b) {

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

+36-29
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import java.util.function.Function;
2222
import lombok.extern.slf4j.Slf4j;
2323

24-
/** OpenFeature provider for flagd. */
24+
/**
25+
* OpenFeature provider for flagd.
26+
*/
2527
@Slf4j
2628
@SuppressWarnings({"PMD.TooManyStaticImports", "checkstyle:NoFinalizer"})
2729
public class FlagdProvider extends EventProvider {
@@ -38,7 +40,9 @@ protected final void finalize() {
3840
// DO NOT REMOVE, spotbugs: CT_CONSTRUCTOR_THROW
3941
}
4042

41-
/** Create a new FlagdProvider instance with default options. */
43+
/**
44+
* Create a new FlagdProvider instance with default options.
45+
*/
4246
public FlagdProvider() {
4347
this(FlagdOptions.builder().build());
4448
}
@@ -55,10 +59,7 @@ public FlagdProvider(final FlagdOptions options) {
5559
break;
5660
case Config.RESOLVER_RPC:
5761
this.flagResolver = new GrpcResolver(
58-
options,
59-
new Cache(options.getCacheType(), options.getMaxCacheSize()),
60-
this::isConnected,
61-
this::onConnectionEvent);
62+
options, new Cache(options.getCacheType(), options.getMaxCacheSize()), this::onConnectionEvent);
6263
break;
6364
default:
6465
throw new IllegalStateException(
@@ -80,7 +81,7 @@ public synchronized void initialize(EvaluationContext evaluationContext) throws
8081
}
8182

8283
this.flagResolver.init();
83-
this.initialized = true;
84+
this.initialized = this.connected = true;
8485
}
8586

8687
@Override
@@ -129,8 +130,10 @@ public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultVa
129130
}
130131

131132
/**
132-
* An unmodifiable view of a Structure representing the latest result of the SyncMetadata. Set on
133-
* initial connection and updated with every reconnection. see:
133+
* An unmodifiable view of a Structure representing the latest result of the
134+
* SyncMetadata.
135+
* Set on initial connection and updated with every reconnection.
136+
* see:
134137
* https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.FlagSyncService.GetMetadata
135138
*
136139
* @return Object map representing sync metadata
@@ -153,38 +156,42 @@ private boolean isConnected() {
153156
}
154157

155158
private void onConnectionEvent(ConnectionEvent connectionEvent) {
156-
boolean previous = connected;
157-
boolean current = connected = connectionEvent.isConnected();
159+
final boolean wasConnected = connected;
160+
final boolean isConnected = connected = connectionEvent.isConnected();
161+
158162
syncMetadata = connectionEvent.getSyncMetadata();
159163
enrichedContext = contextEnricher.apply(connectionEvent.getSyncMetadata());
160164

161-
// configuration changed
162-
if (initialized && previous && current) {
163-
log.debug("Configuration changed");
164-
ProviderEventDetails details = ProviderEventDetails.builder()
165-
.flagsChanged(connectionEvent.getFlagsChanged())
166-
.message("configuration changed")
167-
.build();
168-
this.emitProviderConfigurationChanged(details);
165+
if (!initialized) {
169166
return;
170167
}
171-
// there was an error
172-
if (initialized && previous && !current) {
173-
log.debug("There has been an error");
168+
169+
if (!wasConnected && isConnected) {
174170
ProviderEventDetails details = ProviderEventDetails.builder()
175-
.message("there has been an error")
171+
.flagsChanged(connectionEvent.getFlagsChanged())
172+
.message("connected to flagd")
176173
.build();
177-
this.emitProviderError(details);
174+
this.emitProviderReady(details);
178175
return;
179176
}
180-
// we recovered from an error
181-
if (initialized && !previous && current) {
182-
log.debug("Recovered from error");
177+
178+
if (wasConnected && isConnected) {
183179
ProviderEventDetails details = ProviderEventDetails.builder()
184-
.message("recovered from error")
180+
.flagsChanged(connectionEvent.getFlagsChanged())
181+
.message("configuration changed")
185182
.build();
186-
this.emitProviderReady(details);
187183
this.emitProviderConfigurationChanged(details);
184+
return;
185+
}
186+
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());
188195
}
189196
}
190197
}

0 commit comments

Comments
 (0)