Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/sdk/server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ ext.versions = [
"jackson": "2.11.2",
"jsr305": "3.0.2",
"launchdarklyJavaSdkCommon": "2.3.0",
"launchdarklyJavaSdkInternal": "1.12.2",
"launchdarklyJavaSdkInternal": "1.13.0",
"launchdarklyLogging": "1.1.0",
"okhttp": "4.12.0", // specify this for the SDK build instead of relying on the transitive dependency from okhttp-eventsource
"okhttpEventsource": "5.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class TestService {
"context-comparison",
"context-type",
"evaluation-hooks",
"event-delivery-cooldown",
"event-gzip",
"event-sampling",
"filtering",
Expand Down
5 changes: 5 additions & 0 deletions lib/sdk/server/contract-tests/test-suppressions-fdv2.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
streaming/fdv2/recoverable fallback to secondary synchronizer
streaming/fdv2/recoverable fallback with recovery
streaming/fdv2/permanent fallback with recovery
# launchdarkly-java-sdk-internal's HttpErrors.classifyHttpFailure treats a 413 as an unexpected
# failure (it has no locally-unrecoverable classification), so a 413 opens a cooldown window
# instead of only dropping the oversized payload (SDK-3513 owns the fix and the removal of this
# suppression).
events/delivery cooldown/413 is dropped without disabling delivery
6 changes: 5 additions & 1 deletion lib/sdk/server/contract-tests/test-suppressions.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@

# launchdarkly-java-sdk-internal's HttpErrors.classifyHttpFailure treats a 413 as an unexpected
# failure (it has no locally-unrecoverable classification), so a 413 opens a cooldown window
# instead of only dropping the oversized payload (SDK-3513 owns the fix and the removal of this
# suppression).
events/delivery cooldown/413 is dropped without disabling delivery
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,11 @@ public Result sendDiagnosticEvent(byte[] data, URI eventsBaseUri) {
private Result transformResult(com.launchdarkly.sdk.server.subsystems.EventSender.Result result) {
switch (result) {
case FAILURE:
return new Result(false, false, null);
return Result.failure();
case STOP:
return new Result(false, true, null);
return Result.unexpectedFailure();
default:
return new Result(true, false, null);
return Result.success(null);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,19 @@ public enum Result {
FAILURE,

/**
* The EventSender was not able to deliver the events, and the nature of the error indicates that
* the SDK should not attempt to send any more events.
* The EventSender was not able to deliver the events, and the nature of the error (such as an
* HTTP 401 error, implying that the SDK key is invalid) indicates that the SDK should stop
* attempting delivery for a while.
* <p>
* The SDK responds by entering a bounded cooldown window. While the window is open, the SDK
* initiates no new analytics-event deliveries through this EventSender (a delivery already
* dispatched when the window opened still completes), and analytics events that would have
* been sent during the window are discarded rather than held. Diagnostic-event calls are not
* suppressed, and this result from one of them opens a window of its own. If delivery is
* still failing this way when a window elapses, the next window is longer, up to a ceiling;
* sustained successful delivery returns it to the initial length. Delivery resumes on its own
* once a window has elapsed; no application action is required, and event delivery is never
* disabled permanently. Returning {@link #FAILURE} instead never opens or lengthens a window.
*/
STOP
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.launchdarkly.sdk.server;

import com.launchdarkly.sdk.server.ComponentsImpl.EventProcessorBuilderImpl.EventSenderWrapper;
import com.launchdarkly.sdk.server.subsystems.EventSender;

import org.junit.Test;

import java.io.IOException;
import java.net.URI;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

@SuppressWarnings("javadoc")
public class EventSenderWrapperTest {
private static final URI FAKE_URI = URI.create("http://fake");
private static final byte[] FAKE_DATA = new byte[] {1, 2, 3};

private static final class RecordingSender implements EventSender {
private final Result result;
String methodCalled;
byte[] receivedData;
int receivedEventCount = -1;
URI receivedUri;

RecordingSender(Result result) {
this.result = result;
}

@Override
public Result sendAnalyticsEvents(byte[] data, int eventCount, URI eventsBaseUri) {
methodCalled = "analytics";
receivedData = data;
receivedEventCount = eventCount;
receivedUri = eventsBaseUri;
return result;
}

@Override
public Result sendDiagnosticEvent(byte[] data, URI eventsBaseUri) {
methodCalled = "diagnostic";
receivedData = data;
receivedUri = eventsBaseUri;
return result;
}

@Override
public void close() throws IOException {}
}

@Test
public void successMapsToSuccessfulDelivery() {
RecordingSender sender = new RecordingSender(EventSender.Result.SUCCESS);
com.launchdarkly.sdk.internal.events.EventSender.Result result =
new EventSenderWrapper(sender).sendAnalyticsEvents(FAKE_DATA, 3, FAKE_URI);

assertTrue(result.isSuccess());
assertFalse(result.isUnexpectedFailure());
assertNull(result.getTimeFromServer());

// The wrapper routes to the analytics method with the arguments unchanged.
assertEquals("analytics", sender.methodCalled);
assertSame(FAKE_DATA, sender.receivedData);
assertEquals(3, sender.receivedEventCount);
assertSame(FAKE_URI, sender.receivedUri);
}

@Test
public void failureMapsToOrdinaryFailure() {
RecordingSender sender = new RecordingSender(EventSender.Result.FAILURE);
com.launchdarkly.sdk.internal.events.EventSender.Result result =
new EventSenderWrapper(sender).sendAnalyticsEvents(FAKE_DATA, 1, FAKE_URI);

assertFalse(result.isSuccess());
assertFalse(result.isUnexpectedFailure());
}

@SuppressWarnings("deprecation")
@Test
public void stopMapsToUnexpectedFailure() {
RecordingSender sender = new RecordingSender(EventSender.Result.STOP);
com.launchdarkly.sdk.internal.events.EventSender.Result result =
new EventSenderWrapper(sender).sendAnalyticsEvents(FAKE_DATA, 1, FAKE_URI);

assertFalse(result.isSuccess());
assertTrue(result.isUnexpectedFailure());
// The legacy flag stays written for consumers that have not migrated off it.
assertTrue(result.isMustShutDown());
}

@Test
public void diagnosticPathUsesTheSameMappingAndRouting() {
RecordingSender stopSender = new RecordingSender(EventSender.Result.STOP);
com.launchdarkly.sdk.internal.events.EventSender.Result stopResult =
new EventSenderWrapper(stopSender).sendDiagnosticEvent(FAKE_DATA, FAKE_URI);
assertTrue(stopResult.isUnexpectedFailure());
assertEquals("diagnostic", stopSender.methodCalled);
assertSame(FAKE_DATA, stopSender.receivedData);
assertSame(FAKE_URI, stopSender.receivedUri);

RecordingSender successSender = new RecordingSender(EventSender.Result.SUCCESS);
assertTrue(new EventSenderWrapper(successSender)
.sendDiagnosticEvent(FAKE_DATA, FAKE_URI).isSuccess());
assertEquals("diagnostic", successSender.methodCalled);
}
}
Loading