Skip to content

Commit 3f774f4

Browse files
author
Bernd Warmuth
committed
feat: implement grpc reconnect for inprocess mode
Signed-off-by: Bernd Warmuth <[email protected]>
1 parent f06d895 commit 3f774f4

File tree

5 files changed

+35
-339
lines changed

5 files changed

+35
-339
lines changed

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/grpc/GrpcConnector.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.grpc;
22

3-
import com.google.common.annotations.VisibleForTesting;
43
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
54
import dev.openfeature.contrib.providers.flagd.resolver.common.ChannelBuilder;
65
import dev.openfeature.contrib.providers.flagd.resolver.common.ChannelMonitor;
@@ -125,8 +124,7 @@ public GrpcConnector(
125124
* @param onConnectionEvent a consumer to handle connection events
126125
* @param eventStreamObserver a consumer to handle the event stream
127126
*/
128-
@VisibleForTesting
129-
GrpcConnector(
127+
public GrpcConnector(
130128
final FlagdOptions options,
131129
final Function<ManagedChannel, T> stub,
132130
final Function<ManagedChannel, K> blockingStub,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public InProcessResolver(
5757
FlagdOptions options,
5858
final Supplier<Boolean> connectedSupplier,
5959
Consumer<ConnectionEvent> onConnectionEvent) {
60-
this.flagStore = new FlagStore(getConnector(options));
60+
this.flagStore = new FlagStore(getConnector(options, onConnectionEvent));
6161
this.deadline = options.getDeadline();
6262
this.onConnectionEvent = onConnectionEvent;
6363
this.operator = new Operator();
@@ -160,14 +160,14 @@ public ProviderEvaluation<Value> objectEvaluation(String key, Value defaultValue
160160
.build();
161161
}
162162

163-
static Connector getConnector(final FlagdOptions options) {
163+
static Connector getConnector(final FlagdOptions options, Consumer<ConnectionEvent> onConnectionEvent) {
164164
if (options.getCustomConnector() != null) {
165165
return options.getCustomConnector();
166166
}
167167
return options.getOfflineFlagSourcePath() != null
168168
&& !options.getOfflineFlagSourcePath().isEmpty()
169169
? new FileConnector(options.getOfflineFlagSourcePath())
170-
: new GrpcStreamConnector(options);
170+
: new GrpcStreamConnector(options, onConnectionEvent);
171171
}
172172

173173
private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationContext ctx) {
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.grpc;
22

33
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
4-
import dev.openfeature.contrib.providers.flagd.resolver.common.ChannelBuilder;
5-
import dev.openfeature.contrib.providers.flagd.resolver.common.backoff.GrpcStreamConnectorBackoffService;
4+
import dev.openfeature.contrib.providers.flagd.resolver.common.ConnectionEvent;
5+
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcConnector;
66
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector;
77
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.QueuePayload;
88
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.QueuePayloadType;
99
import dev.openfeature.flagd.grpc.sync.FlagSyncServiceGrpc;
10-
import dev.openfeature.flagd.grpc.sync.FlagSyncServiceGrpc.FlagSyncServiceBlockingStub;
11-
import dev.openfeature.flagd.grpc.sync.FlagSyncServiceGrpc.FlagSyncServiceStub;
1210
import dev.openfeature.flagd.grpc.sync.Sync.GetMetadataRequest;
1311
import dev.openfeature.flagd.grpc.sync.Sync.GetMetadataResponse;
1412
import dev.openfeature.flagd.grpc.sync.Sync.SyncFlagsRequest;
1513
import dev.openfeature.flagd.grpc.sync.Sync.SyncFlagsResponse;
1614
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1715
import io.grpc.Context;
1816
import io.grpc.Context.CancellableContext;
19-
import io.grpc.ManagedChannel;
2017
import java.util.concurrent.BlockingQueue;
2118
import java.util.concurrent.LinkedBlockingQueue;
2219
import java.util.concurrent.TimeUnit;
2320
import java.util.concurrent.atomic.AtomicBoolean;
21+
import java.util.function.Consumer;
2422
import lombok.extern.slf4j.Slf4j;
25-
import org.slf4j.event.Level;
2623

2724
/**
2825
* Implements the {@link Connector} contract and emit flags obtained from flagd sync gRPC contract.
@@ -36,42 +33,34 @@ public class GrpcStreamConnector implements Connector {
3633

3734
private final AtomicBoolean shutdown = new AtomicBoolean(false);
3835
private final BlockingQueue<QueuePayload> blockingQueue = new LinkedBlockingQueue<>(QUEUE_SIZE);
39-
private final ManagedChannel channel;
40-
private final FlagSyncServiceStub serviceStub;
41-
private final FlagSyncServiceBlockingStub serviceBlockingStub;
4236
private final int deadline;
43-
private final int streamDeadlineMs;
4437
private final String selector;
45-
private final int retryBackoffMillis;
38+
private final GrpcConnector<
39+
FlagSyncServiceGrpc.FlagSyncServiceStub, FlagSyncServiceGrpc.FlagSyncServiceBlockingStub>
40+
grpcConnector;
41+
private final LinkedBlockingQueue<GrpcResponseModel> streamReceiver;
4642

4743
/**
48-
* Construct a new GrpcStreamConnector.
49-
*
50-
* @param options flagd options
44+
* Creates a new GrpcStreamConnector responsible for observing the event stream.
5145
*/
52-
public GrpcStreamConnector(final FlagdOptions options) {
53-
channel = ChannelBuilder.nettyChannel(options);
54-
serviceStub = FlagSyncServiceGrpc.newStub(channel);
55-
serviceBlockingStub = FlagSyncServiceGrpc.newBlockingStub(channel);
46+
public GrpcStreamConnector(final FlagdOptions options, Consumer<ConnectionEvent> onConnectionEvent) {
5647
deadline = options.getDeadline();
57-
streamDeadlineMs = options.getStreamDeadlineMs();
5848
selector = options.getSelector();
59-
retryBackoffMillis = options.getRetryBackoffMs();
49+
streamReceiver = new LinkedBlockingQueue<>(QUEUE_SIZE);
50+
grpcConnector = new GrpcConnector<>(
51+
options,
52+
FlagSyncServiceGrpc::newStub,
53+
FlagSyncServiceGrpc::newBlockingStub,
54+
onConnectionEvent,
55+
stub -> stub.syncFlags(SyncFlagsRequest.getDefaultInstance(), new GrpcStreamHandler(streamReceiver)));
6056
}
6157

6258
/** Initialize gRPC stream connector. */
63-
public void init() {
59+
public void init() throws Exception {
60+
grpcConnector.initialize();
6461
Thread listener = new Thread(() -> {
6562
try {
66-
observeEventStream(
67-
blockingQueue,
68-
shutdown,
69-
serviceStub,
70-
serviceBlockingStub,
71-
selector,
72-
deadline,
73-
streamDeadlineMs,
74-
retryBackoffMillis);
63+
observeEventStream(blockingQueue, shutdown, selector, deadline);
7564
} catch (InterruptedException e) {
7665
log.warn("gRPC event stream interrupted, flag configurations are stale", e);
7766
Thread.currentThread().interrupt();
@@ -96,37 +85,17 @@ public void shutdown() throws InterruptedException {
9685
if (shutdown.getAndSet(true)) {
9786
return;
9887
}
99-
100-
try {
101-
if (this.channel != null && !this.channel.isShutdown()) {
102-
this.channel.shutdown();
103-
this.channel.awaitTermination(this.deadline, TimeUnit.MILLISECONDS);
104-
}
105-
} finally {
106-
if (this.channel != null && !this.channel.isShutdown()) {
107-
this.channel.shutdownNow();
108-
this.channel.awaitTermination(this.deadline, TimeUnit.MILLISECONDS);
109-
log.warn(String.format("Unable to shut down channel by %d deadline", this.deadline));
110-
}
111-
}
88+
this.grpcConnector.shutdown();
11289
}
11390

11491
/** Contains blocking calls, to be used concurrently. */
115-
static void observeEventStream(
92+
void observeEventStream(
11693
final BlockingQueue<QueuePayload> writeTo,
11794
final AtomicBoolean shutdown,
118-
final FlagSyncServiceStub serviceStub,
119-
final FlagSyncServiceBlockingStub serviceBlockingStub,
12095
final String selector,
121-
final int deadline,
122-
final int streamDeadlineMs,
123-
int retryBackoffMillis)
96+
final int deadline)
12497
throws InterruptedException {
12598

126-
final BlockingQueue<GrpcResponseModel> streamReceiver = new LinkedBlockingQueue<>(QUEUE_SIZE);
127-
final GrpcStreamConnectorBackoffService backoffService =
128-
new GrpcStreamConnectorBackoffService(retryBackoffMillis);
129-
13099
log.info("Initializing sync stream observer");
131100

132101
while (!shutdown.get()) {
@@ -143,15 +112,10 @@ static void observeEventStream(
143112
}
144113

145114
try (CancellableContext context = Context.current().withCancellation()) {
146-
FlagSyncServiceStub localServiceStub = serviceStub;
147-
if (streamDeadlineMs > 0) {
148-
localServiceStub = localServiceStub.withDeadlineAfter(streamDeadlineMs, TimeUnit.MILLISECONDS);
149-
}
150-
151-
localServiceStub.syncFlags(syncRequest.build(), new GrpcStreamHandler(streamReceiver));
152115

153116
try {
154-
metadataResponse = serviceBlockingStub
117+
metadataResponse = grpcConnector
118+
.getResolver()
155119
.withDeadlineAfter(deadline, TimeUnit.MILLISECONDS)
156120
.getMetadata(metadataRequest.build());
157121
} catch (Exception e) {
@@ -164,27 +128,18 @@ static void observeEventStream(
164128

165129
while (!shutdown.get()) {
166130
final GrpcResponseModel response = streamReceiver.take();
167-
168131
if (response.isComplete()) {
169132
log.info("Sync stream completed");
170-
// The stream is complete, this isn't really an error but we should try to
133+
// The stream is complete, this isn't really an error, but we should try to
171134
// reconnect
172135
break;
173136
}
174137

175138
Throwable streamException = response.getError();
176139
if (streamException != null || metadataException != null) {
177-
long retryDelay = backoffService.getCurrentBackoffMillis();
178-
179-
// if we are in silent recover mode, we should not expose the error to the client
180-
if (backoffService.shouldRetrySilently()) {
181-
logExceptions(Level.INFO, streamException, metadataException, retryDelay);
182-
} else {
183-
logExceptions(Level.ERROR, streamException, metadataException, retryDelay);
184-
if (!writeTo.offer(new QueuePayload(
185-
QueuePayloadType.ERROR, "Error from stream or metadata", metadataResponse))) {
186-
log.error("Failed to convey ERROR status, queue is full");
187-
}
140+
if (!writeTo.offer(new QueuePayload(
141+
QueuePayloadType.ERROR, "Error from stream or metadata", metadataResponse))) {
142+
log.error("Failed to convey ERROR status, queue is full");
188143
}
189144

190145
// close the context to cancel the stream in case just the metadata call failed
@@ -199,34 +154,10 @@ static void observeEventStream(
199154
if (!writeTo.offer(new QueuePayload(QueuePayloadType.DATA, data, metadataResponse))) {
200155
log.error("Stream writing failed");
201156
}
202-
203-
// reset backoff if we succeeded in a retry attempt
204-
backoffService.reset();
205157
}
206158
}
207-
208-
// check for shutdown and avoid sleep
209-
if (!shutdown.get()) {
210-
log.debug("Stream failed, retrying in {}ms", backoffService.getCurrentBackoffMillis());
211-
backoffService.waitUntilNextAttempt();
212-
}
213159
}
214160

215161
log.info("Shutdown invoked, exiting event stream listener");
216162
}
217-
218-
private static void logExceptions(
219-
Level logLevel, Throwable streamException, Exception metadataException, long retryDelay) {
220-
if (streamException != null) {
221-
log.atLevel(logLevel)
222-
.setCause(streamException)
223-
.log("Error initializing stream, retrying in {}ms", retryDelay);
224-
}
225-
226-
if (metadataException != null) {
227-
log.atLevel(logLevel)
228-
.setCause(metadataException)
229-
.log("Error initializing metadata, retrying in {}ms", retryDelay);
230-
}
231-
}
232163
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ public void connectorSetup() {
6767
.build();
6868

6969
// then
70-
assertInstanceOf(GrpcStreamConnector.class, InProcessResolver.getConnector(forGrpcOptions));
71-
assertInstanceOf(FileConnector.class, InProcessResolver.getConnector(forOfflineOptions));
72-
assertInstanceOf(MockConnector.class, InProcessResolver.getConnector(forCustomConnectorOptions));
70+
assertInstanceOf(GrpcStreamConnector.class, InProcessResolver.getConnector(forGrpcOptions, e -> {}));
71+
assertInstanceOf(FileConnector.class, InProcessResolver.getConnector(forOfflineOptions, e -> {}));
72+
assertInstanceOf(MockConnector.class, InProcessResolver.getConnector(forCustomConnectorOptions, e -> {}));
7373
}
7474

7575
@Test

0 commit comments

Comments
 (0)