Skip to content

Commit 0891c8a

Browse files
committed
fixup: fail init and delay parsing, readme
Signed-off-by: Todd Baert <[email protected]>
1 parent 8b0bc85 commit 0891c8a

File tree

10 files changed

+122
-96
lines changed

10 files changed

+122
-96
lines changed

providers/flagd/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ FlagdProvider flagdProvider = new FlagdProvider(
4747

4848
In the above example, in-process handlers attempt to connect to a sync service on address `localhost:8013` to obtain [flag definitions](https://github.com/open-feature/schemas/blob/main/json/flags.json).
4949

50+
#### Sync-metadata
51+
52+
To support the injection of contextual data configured in flagd for in-process evaluation, the provider exposes a `getSyncMetadata` accessor which provides the most recent value returned by the [GetMetadata RPC](https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.FlagSyncService.GetMetadata).
53+
The value is updated with every (re)connection to the sync implementation.
54+
This can be used to enrich evaluations with such data.
55+
If the `in-process` mode is not used, and before the provider is ready, the `getSyncMetadata` returns an empty map.
56+
5057
#### Offline mode
5158

5259
In-process resolvers can also work in an offline mode.

providers/flagd/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
</properties>
2020

2121
<name>flagd</name>
22-
<description>FlagD provider for Java</description>
22+
<description>flagd provider for Java</description>
2323
<url>https://openfeature.dev</url>
2424

2525
<developers>

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
@Slf4j
2323
@SuppressWarnings({ "PMD.TooManyStaticImports", "checkstyle:NoFinalizer" })
2424
public class FlagdProvider extends EventProvider {
25-
private static final String FLAGD_PROVIDER = "flagD Provider";
25+
private static final String FLAGD_PROVIDER = "flagd Provider";
2626
private final Resolver flagResolver;
2727
private volatile boolean initialized = false;
2828
private volatile boolean connected = false;

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

+29-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.storage;
22

3-
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
4-
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FlagParser;
5-
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector;
6-
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.QueuePayload;
7-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
8-
import lombok.extern.slf4j.Slf4j;
3+
import static dev.openfeature.contrib.providers.flagd.resolver.common.Convert.convertProtobufMapToStructure;
4+
5+
import java.util.Collections;
96
import java.util.HashMap;
107
import java.util.List;
118
import java.util.Map;
@@ -17,6 +14,14 @@
1714
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
1815
import java.util.stream.Collectors;
1916

17+
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
18+
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FlagParser;
19+
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector;
20+
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.QueuePayload;
21+
import dev.openfeature.flagd.grpc.sync.Sync.GetMetadataResponse;
22+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
23+
import lombok.extern.slf4j.Slf4j;
24+
2025
/**
2126
* Feature flag storage.
2227
*/
@@ -97,12 +102,14 @@ private void streamerListener(final Connector connector) throws InterruptedExcep
97102
final BlockingQueue<QueuePayload> streamPayloads = connector.getStream();
98103

99104
while (!shutdown.get()) {
100-
final QueuePayload take = streamPayloads.take();
101-
switch (take.getType()) {
105+
final QueuePayload payload = streamPayloads.take();
106+
switch (payload.getType()) {
102107
case DATA:
103108
try {
104109
List<String> changedFlagsKeys;
105-
Map<String, FeatureFlag> flagMap = FlagParser.parseString(take.getFlagData(), throwIfInvalid);
110+
Map<String, FeatureFlag> flagMap = FlagParser.parseString(payload.getFlagData(),
111+
throwIfInvalid);
112+
Map<String, Object> metadata = parseSyncMetadata(payload.getMetadataResponse());
106113
writeLock.lock();
107114
try {
108115
changedFlagsKeys = getChangedFlagsKeys(flagMap);
@@ -111,7 +118,8 @@ private void streamerListener(final Connector connector) throws InterruptedExcep
111118
} finally {
112119
writeLock.unlock();
113120
}
114-
if (!stateBlockingQueue.offer(new StorageStateChange(StorageState.OK, changedFlagsKeys))) {
121+
if (!stateBlockingQueue
122+
.offer(new StorageStateChange(StorageState.OK, changedFlagsKeys, metadata))) {
115123
log.warn("Failed to convey OK satus, queue is full");
116124
}
117125
} catch (Throwable e) {
@@ -128,13 +136,23 @@ private void streamerListener(final Connector connector) throws InterruptedExcep
128136
}
129137
break;
130138
default:
131-
log.info(String.format("Payload with unknown type: %s", take.getType()));
139+
log.info(String.format("Payload with unknown type: %s", payload.getType()));
132140
}
133141
}
134142

135143
log.info("Shutting down store stream listener");
136144
}
137145

146+
private Map<String, Object> parseSyncMetadata(GetMetadataResponse metadataResponse) {
147+
try {
148+
return convertProtobufMapToStructure(metadataResponse.getMetadata().getFieldsMap())
149+
.asObjectMap();
150+
} catch (Exception exception) {
151+
log.error("Failed to parse metadataResponse, provider metadata may not be up-to-date");
152+
}
153+
return Collections.emptyMap();
154+
}
155+
138156
private List<String> getChangedFlagsKeys(Map<String, FeatureFlag> newFlags) {
139157
Map<String, FeatureFlag> changedFlags = new HashMap<>();
140158
Map<String, FeatureFlag> addedFeatureFlags = new HashMap<>();

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/connector/QueuePayload.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector;
22

3-
import java.util.Map;
3+
import dev.openfeature.flagd.grpc.sync.Sync.GetMetadataResponse;
44
import lombok.AllArgsConstructor;
55
import lombok.Getter;
66

@@ -12,5 +12,9 @@
1212
public class QueuePayload {
1313
private final QueuePayloadType type;
1414
private final String flagData;
15-
private final Map<String, Object> syncMetadata;
15+
private final GetMetadataResponse metadataResponse;
16+
17+
public QueuePayload(QueuePayloadType type, String flagData) {
18+
this(type, flagData, GetMetadataResponse.getDefaultInstance());
19+
}
1620
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/connector/file/FileConnector.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.nio.file.Files;
66
import java.nio.file.Path;
77
import java.nio.file.Paths;
8-
import java.util.Collections;
98
import java.util.concurrent.BlockingQueue;
109
import java.util.concurrent.LinkedBlockingQueue;
1110

@@ -46,7 +45,7 @@ public void init() throws IOException {
4645

4746
// initial read
4847
String flagData = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8);
49-
if (!queue.offer(new QueuePayload(QueuePayloadType.DATA, flagData, Collections.emptyMap()))) {
48+
if (!queue.offer(new QueuePayload(QueuePayloadType.DATA, flagData))) {
5049
log.warn(OFFER_WARN);
5150
}
5251

@@ -59,7 +58,7 @@ public void init() throws IOException {
5958
if (currentTS > lastTS) {
6059
lastTS = currentTS;
6160
flagData = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8);
62-
if (!queue.offer(new QueuePayload(QueuePayloadType.DATA, flagData, Collections.emptyMap()))) {
61+
if (!queue.offer(new QueuePayload(QueuePayloadType.DATA, flagData))) {
6362
log.warn(OFFER_WARN);
6463
}
6564
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/connector/grpc/GrpcStreamConnector.java

+46-51
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.grpc;
22

3-
import static dev.openfeature.contrib.providers.flagd.resolver.common.Convert.convertProtobufMapToStructure;
4-
5-
import java.util.Collections;
6-
import java.util.Map;
73
import java.util.Random;
84
import java.util.concurrent.BlockingQueue;
95
import java.util.concurrent.LinkedBlockingQueue;
@@ -23,6 +19,8 @@
2319
import dev.openfeature.flagd.grpc.sync.Sync.SyncFlagsRequest;
2420
import dev.openfeature.flagd.grpc.sync.Sync.SyncFlagsResponse;
2521
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
22+
import io.grpc.Context;
23+
import io.grpc.Context.CancellableContext;
2624
import io.grpc.ManagedChannel;
2725
import lombok.extern.slf4j.Slf4j;
2826

@@ -125,68 +123,66 @@ static void observeEventStream(final BlockingQueue<QueuePayload> writeTo,
125123
log.info("Initializing sync stream observer");
126124

127125
while (!shutdown.get()) {
126+
writeTo.clear();
128127
Exception metadataException = null;
129128
log.debug("Initializing sync stream request");
130129
final SyncFlagsRequest.Builder syncRequest = SyncFlagsRequest.newBuilder();
131130
final GetMetadataRequest.Builder metadataRequest = GetMetadataRequest.newBuilder();
132-
Map<String, Object> metadata = Collections.emptyMap();
131+
GetMetadataResponse metadataResponse = GetMetadataResponse.getDefaultInstance();
133132

134133
if (selector != null) {
135134
syncRequest.setSelector(selector);
136135
}
137136

138-
serviceStub.syncFlags(syncRequest.build(), new GrpcStreamHandler(streamReceiver));
139-
try {
140-
GetMetadataResponse metadataResponse = serviceBlockingStub
141-
.withDeadlineAfter(deadline, TimeUnit.MILLISECONDS).getMetadata(metadataRequest.build());
142-
metadata = convertProtobufMapToStructure(metadataResponse.getMetadata().getFieldsMap()).asObjectMap();
143-
} catch (Exception e) {
144-
// the chances this call fails but the syncRequest does not are slim
145-
// it could be that the server doesn't implement this RPC
146-
// instead of logging here, retain the exception and only log if the
147-
// streamReceiver doesn't error
148-
metadataException = e;
149-
}
150-
151-
while (!shutdown.get()) {
152-
final GrpcResponseModel response = streamReceiver.take();
153-
154-
if (response.isComplete()) {
155-
log.info("Sync stream completed");
156-
// The stream is complete, this isn't really an error but we should try to
157-
// reconnect
158-
break;
137+
try (CancellableContext context = Context.current().withCancellation()) {
138+
serviceStub.syncFlags(syncRequest.build(), new GrpcStreamHandler(streamReceiver));
139+
try {
140+
metadataResponse = serviceBlockingStub.withDeadlineAfter(deadline, TimeUnit.MILLISECONDS)
141+
.getMetadata(metadataRequest.build());
142+
} catch (Exception e) {
143+
// the chances this call fails but the syncRequest does not are slim
144+
// it could be that the server doesn't implement this RPC
145+
// instead of logging and throwing here, retain the exception and handle in the
146+
// stream logic below
147+
metadataException = e;
159148
}
160149

161-
if (response.getError() != null) {
162-
log.error(String.format("Error from grpc connection, retrying in %dms", retryDelay),
163-
response.getError());
150+
while (!shutdown.get()) {
151+
final GrpcResponseModel response = streamReceiver.take();
164152

165-
if (!writeTo.offer(
166-
new QueuePayload(QueuePayloadType.ERROR, "Error from stream connection, retrying",
167-
metadata))) {
168-
log.error("Failed to convey ERROR status, queue is full");
153+
if (response.isComplete()) {
154+
log.info("Sync stream completed");
155+
// The stream is complete, this isn't really an error but we should try to
156+
// reconnect
157+
break;
169158
}
170-
break;
171-
}
172159

173-
final SyncFlagsResponse flagsResponse = response.getSyncFlagsResponse();
174-
String data = flagsResponse.getFlagConfiguration();
175-
log.debug("Got stream response: " + data);
160+
if (response.getError() != null || metadataException != null) {
161+
log.error(String.format("Error from initializing stream or metadata, retrying in %dms",
162+
retryDelay), response.getError());
163+
164+
if (!writeTo.offer(
165+
new QueuePayload(QueuePayloadType.ERROR, "Error from stream or metadata",
166+
metadataResponse))) {
167+
log.error("Failed to convey ERROR status, queue is full");
168+
}
169+
// close the context to cancel the stream in case just the metadata call failed
170+
context.cancel(metadataException);
171+
break;
172+
}
176173

177-
if (!writeTo.offer(
178-
new QueuePayload(QueuePayloadType.DATA, data, metadata))) {
179-
log.error("Stream writing failed");
180-
}
174+
final SyncFlagsResponse flagsResponse = response.getSyncFlagsResponse();
175+
String data = flagsResponse.getFlagConfiguration();
176+
log.debug("Got stream response: " + data);
181177

182-
if (metadataException != null) {
183-
// if we somehow are connected but the metadata call failed, something strange
184-
// happened
185-
log.error("Stream connected but getMetadata RPC failed", metadataException);
186-
}
178+
if (!writeTo.offer(
179+
new QueuePayload(QueuePayloadType.DATA, data, metadataResponse))) {
180+
log.error("Stream writing failed");
181+
}
187182

188-
// reset retry delay if we succeeded in a retry attempt
189-
retryDelay = INIT_BACK_OFF;
183+
// reset retry delay if we succeeded in a retry attempt
184+
retryDelay = INIT_BACK_OFF;
185+
}
190186
}
191187

192188
// check for shutdown and avoid sleep
@@ -196,7 +192,7 @@ static void observeEventStream(final BlockingQueue<QueuePayload> writeTo,
196192
}
197193

198194
// busy wait till next attempt
199-
log.warn(String.format("Stream failed, retrying in %dms", retryDelay));
195+
log.debug(String.format("Stream failed, retrying in %dms", retryDelay));
200196
Thread.sleep(retryDelay + RANDOM.nextInt(INIT_BACK_OFF));
201197

202198
if (retryDelay < MAX_BACK_OFF) {
@@ -207,5 +203,4 @@ static void observeEventStream(final BlockingQueue<QueuePayload> writeTo,
207203
// log as this can happen after awakened from backoff sleep
208204
log.info("Shutdown invoked, exiting event stream listener");
209205
}
210-
211206
}

0 commit comments

Comments
 (0)