Skip to content

Commit 77c0a74

Browse files
chore: Finish goodbye handling. (#130)
<!-- CURSOR_SUMMARY --> > [!NOTE] > **Low Risk** > Low risk: primarily adds logging and threads a `reason` string through `GOODBYE` status results; behavior changes are limited to observability and an extra status field. > > **Overview** > FDv2 `GOODBYE` handling now **captures and exposes the server-provided reason** end-to-end: `FDv2SourceResult.Status` adds a `reason` field (with `getReason()`), and `FDv2SourceResult.goodbye()` now stores that reason instead of discarding it. > > Both polling (`PollingBase`) and streaming (`StreamingSynchronizerImpl`) paths now **log an info message** when a `goodbye` is received and pass the extracted reason into `FDv2SourceResult.goodbye(...)`. Tests are updated to assert `reason` is null for non-goodbye statuses and equals the expected string for goodbye events. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b20c3c4. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Todd Anderson <127344469+tanderson-ld@users.noreply.github.com>
1 parent 92095e7 commit 77c0a74

4 files changed

Lines changed: 41 additions & 9 deletions

File tree

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/PollingBase.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ protected CompletableFuture<FDv2SourceResult> poll(Selector selector, boolean on
138138
return oneShot ? FDv2SourceResult.terminalError(info, fdv1Fallback) : FDv2SourceResult.interrupted(info, fdv1Fallback);
139139
}
140140
case GOODBYE:
141-
return FDv2SourceResult.goodbye(((FDv2ProtocolHandler.FDv2ActionGoodbye) res).getReason(), fdv1Fallback);
141+
String reason = ((FDv2ProtocolHandler.FDv2ActionGoodbye) res).getReason();
142+
logger.info("Goodbye was received from the LaunchDarkly connection with reason: '{}'.", reason);
143+
return FDv2SourceResult.goodbye(reason, fdv1Fallback);
142144
case NONE:
143145
break;
144146
case INTERNAL_ERROR: {

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/StreamingSynchronizerImpl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ private void handleMessage(MessageEvent event) {
282282
break;
283283

284284
case GOODBYE:
285-
FDv2ProtocolHandler.FDv2ActionGoodbye goodbye = (FDv2ProtocolHandler.FDv2ActionGoodbye) action;
286-
result = FDv2SourceResult.goodbye(goodbye.getReason(), getFallback(event));
285+
String reason = ((FDv2ProtocolHandler.FDv2ActionGoodbye) action).getReason();
286+
logger.info("Goodbye was received from the LaunchDarkly connection with reason: '{}'.", reason);
287+
result = FDv2SourceResult.goodbye(reason, getFallback(event));
287288
// We drop this current connection and attempt to restart the stream.
288289
restartStream();
289290
break;

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/datasources/FDv2SourceResult.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public static class Status {
5454
private final State state;
5555
private final DataSourceStatusProvider.ErrorInfo errorInfo;
5656

57+
private final String reason;
58+
5759
public State getState() {
5860
return state;
5961
}
@@ -62,9 +64,34 @@ public DataSourceStatusProvider.ErrorInfo getErrorInfo() {
6264
return errorInfo;
6365
}
6466

65-
public Status(State state, DataSourceStatusProvider.ErrorInfo errorInfo) {
67+
Status(State state, DataSourceStatusProvider.ErrorInfo errorInfo, String reason) {
6668
this.state = state;
6769
this.errorInfo = errorInfo;
70+
this.reason = reason;
71+
}
72+
73+
public static Status goodbye(String reason) {
74+
return new Status(State.GOODBYE, null, reason);
75+
}
76+
77+
public static Status interrupted(DataSourceStatusProvider.ErrorInfo errorInfo) {
78+
return new Status(State.INTERRUPTED, errorInfo, null);
79+
}
80+
81+
public static Status terminalError(DataSourceStatusProvider.ErrorInfo errorInfo) {
82+
return new Status(State.TERMINAL_ERROR, errorInfo, null);
83+
}
84+
85+
public static Status shutdown() {
86+
return new Status(State.SHUTDOWN, null, null);
87+
}
88+
89+
/**
90+
* If the state is GOODBYE, then this will be the reason. Otherwise, it will be null.
91+
* @return the reason, or null
92+
*/
93+
public String getReason() {
94+
return reason;
6895
}
6996
}
7097

@@ -97,7 +124,7 @@ public static FDv2SourceResult interrupted(DataSourceStatusProvider.ErrorInfo er
97124
public static FDv2SourceResult interrupted(DataSourceStatusProvider.ErrorInfo errorInfo, boolean fdv1Fallback, Function<Void, Void> completionCallback) {
98125
return new FDv2SourceResult(
99126
null,
100-
new Status(State.INTERRUPTED, errorInfo),
127+
Status.interrupted(errorInfo),
101128
ResultType.STATUS,
102129
fdv1Fallback,
103130
completionCallback);
@@ -109,7 +136,7 @@ public static FDv2SourceResult shutdown() {
109136

110137
public static FDv2SourceResult shutdown(Function<Void, Void> completionCallback) {
111138
return new FDv2SourceResult(null,
112-
new Status(State.SHUTDOWN, null),
139+
Status.shutdown(),
113140
ResultType.STATUS,
114141
false,
115142
completionCallback);
@@ -121,7 +148,7 @@ public static FDv2SourceResult terminalError(DataSourceStatusProvider.ErrorInfo
121148

122149
public static FDv2SourceResult terminalError(DataSourceStatusProvider.ErrorInfo errorInfo, boolean fdv1Fallback, Function<Void, Void> completionCallback) {
123150
return new FDv2SourceResult(null,
124-
new Status(State.TERMINAL_ERROR, errorInfo),
151+
Status.terminalError(errorInfo),
125152
ResultType.STATUS,
126153
fdv1Fallback,
127154
completionCallback);
@@ -145,10 +172,9 @@ public static FDv2SourceResult goodbye(String reason, boolean fdv1Fallback) {
145172
}
146173

147174
public static FDv2SourceResult goodbye(String reason, boolean fdv1Fallback, Function<Void, Void> completionCallback) {
148-
// TODO: Goodbye reason.
149175
return new FDv2SourceResult(
150176
null,
151-
new Status(State.GOODBYE, null),
177+
Status.goodbye(reason),
152178
ResultType.STATUS,
153179
fdv1Fallback,
154180
completionCallback);

lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/StreamingSynchronizerImplTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public void httpRecoverableError() throws Exception {
144144
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
145145
assertEquals(FDv2SourceResult.State.INTERRUPTED, result.getStatus().getState());
146146
assertEquals(DataSourceStatusProvider.ErrorKind.ERROR_RESPONSE, result.getStatus().getErrorInfo().getKind());
147+
assertNull(result.getStatus().getReason());
147148

148149
synchronizer.close();
149150
}
@@ -244,6 +245,7 @@ public void shutdownBeforeEventReceived() throws Exception {
244245
assertEquals(FDv2SourceResult.ResultType.STATUS, result.getResultType());
245246
assertEquals(FDv2SourceResult.State.SHUTDOWN, result.getStatus().getState());
246247
assertNull(result.getStatus().getErrorInfo());
248+
assertNull(result.getStatus().getReason());
247249
}
248250
}
249251

@@ -322,6 +324,7 @@ public void goodbyeEventInResponse() throws Exception {
322324
assertNotNull(result1);
323325
assertEquals(FDv2SourceResult.ResultType.STATUS, result1.getResultType());
324326
assertEquals(FDv2SourceResult.State.GOODBYE, result1.getStatus().getState());
327+
assertEquals("service-unavailable", result1.getStatus().getReason());
325328

326329
// Second result should be a changeset from the restarted stream
327330
CompletableFuture<FDv2SourceResult> result2Future = synchronizer.next();

0 commit comments

Comments
 (0)