diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java index 82b8de8d7..0a2dd3b6f 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java @@ -118,8 +118,9 @@ EventProcessorBuilderImpl retryDelaysForTesting(long initialDelayMillis, long no return this; } - // How long events give way to flag values, settable by tests the specified waits would slow. - private DirectEventProcessor.Pacing pacing = DirectEventProcessor.Pacing.SPECIFIED; + // How long events give way to flag values, settable by tests the specified waits would slow. Null means + // the configured delivery delay and the specified gap. + private DirectEventProcessor.Pacing pacing; EventProcessorBuilderImpl pacingForTesting(DirectEventProcessor.Pacing pacing) { this.pacing = pacing; @@ -164,7 +165,7 @@ public EventProcessor build(ClientContext clientContext) { flushIntervalMillis, diagnosticRecordingIntervalMillis, DirectEventProcessor.DEFAULT_CLOSE_BUDGET_MILLIS, - pacing, + pacing != null ? pacing : DirectEventProcessor.Pacing.specified(eventDeliveryDelayMillis), clientContext.isInBackground(), true, // initiallyOffline EventUtil.makeEventsTaskExecutor(), diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DirectEventProcessor.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DirectEventProcessor.java index 7126c0f94..fc4ceab1f 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DirectEventProcessor.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/DirectEventProcessor.java @@ -43,22 +43,28 @@ * one stored batch. They send it under the same payload ID, and LaunchDarkly discards the repeat rather than * counting the events twice. *
- * Flag values come first. Where events persist, what a previous run left waits at launch until a fixed time
- * after the SDK first goes online, and the deliveries this processor starts on its own send one batch at a time,
+ * Flag values come first. What this processor sends on its own waits at launch until
+ * {@link com.launchdarkly.sdk.android.integrations.EventProcessorBuilder#eventDeliveryDelayMillis(long)} after the
+ * SDK first goes online, and where events persist, the deliveries it starts on its own send one batch at a time,
* a gap apart, leaving room for flag requests. Both go by time alone: nothing here hears what the flag data
* source is doing. A delivery the application asks for is never held.
*/
final class DirectEventProcessor implements EventProcessor {
/** How long the deliveries this processor starts on its own wait, so that flag values come first. */
static final class Pacing {
- static final Pacing SPECIFIED = new Pacing(2_000, 1_000);
+ /** The gap the specification gives, after the launch delay the application configured. */
+ static Pacing specified(long launchDelayMillis) {
+ return new Pacing(launchDelayMillis, 1_000);
+ }
+
/** No waits at all, for tests about something else. */
static final Pacing NONE = new Pacing(0, 0);
/**
- * How long after the SDK first goes online that a previous run's events, and the diagnostic init event,
- * wait. It gives the application's startup traffic, the first flag request among it, the network first,
- * and is short enough for an application that dies a few seconds after every launch to still send them.
+ * How long after the SDK first goes online that a previous run's events, this run's periodic delivery,
+ * and the diagnostic init event wait. It gives the application's startup traffic, the first flag request
+ * among it, the network first, and is short enough for an application that dies a few seconds after
+ * every launch to still send them.
*/
final long launchDelayMillis;
/** The least time between one paced batch's answer and the next batch's request. */
@@ -138,16 +144,16 @@ static final class Pacing {
private final Pacing pacing;
/**
- * Whether events give way to flag values: only where the application asked for persistence, like the
- * rest of what persistence brings. Without it there is no previous run to hold back, and delivery is
- * what it was before persistence: no launch gate and no pacing.
+ * Whether the deliveries this processor starts on its own are paced: only where the application asked for
+ * persistence, like the rest of what persistence brings. Without it there is no backlog from a previous
+ * run to spread out. The launch gate does not depend on it.
*/
private final boolean givesWayToFlags;
/**
* Whether the launch gate has opened. It opens once the launch delay has passed since the SDK first went
- * online. Until then neither a previous run's events nor the diagnostic init event are sent. Open from the
- * start where events do not give way. Only set on the scheduler thread.
+ * online. Until then neither a previous run's events, this run's periodic delivery, nor the diagnostic init
+ * event are sent. Open from the start where there is no launch delay. Only set on the scheduler thread.
*/
private volatile boolean launchGateOpen;
@@ -270,7 +276,7 @@ private static final class ReadFailures {
this.closeBudgetMillis = closeBudgetMillis;
this.pacing = pacing;
this.givesWayToFlags = store.isPersisting();
- this.launchGateOpen = !givesWayToFlags;
+ this.launchGateOpen = pacing.launchDelayMillis <= 0;
this.scheduler = scheduler;
this.logger = logger;
this.committer = new EventCommitter(eventBuffer, store, capacity, commitOnCallerThread, commitExecutor,
diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/EventProcessorBuilder.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/EventProcessorBuilder.java
index fa5d53b8f..503178159 100644
--- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/EventProcessorBuilder.java
+++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/integrations/EventProcessorBuilder.java
@@ -61,6 +61,16 @@ public abstract class EventProcessorBuilder implements ComponentConfigurer
+ * This setting is experimental, and not subject to any backwards compatibility guarantees or semantic
+ * versioning.
+ *
+ * The default value is {@link #DEFAULT_EVENT_DELIVERY_DELAY_MILLIS}. A negative value is treated as zero,
+ * and a value above {@link #MAX_EVENT_DELIVERY_DELAY_MILLIS} as that maximum, so an application that ends
+ * soon after every launch still sends what a previous run left.
+ *
+ * @param eventDeliveryDelayMillis how long to hold back, in milliseconds
+ * @return the builder
+ */
+ public EventProcessorBuilder eventDeliveryDelayMillis(long eventDeliveryDelayMillis) {
+ this.eventDeliveryDelayMillis = Math.min(Math.max(eventDeliveryDelayMillis, 0),
+ MAX_EVENT_DELIVERY_DELAY_MILLIS);
+ return this;
+ }
+
/**
* Marks a set of attribute names or subproperties as private.
*
diff --git a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DirectEventProcessorTest.java b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DirectEventProcessorTest.java
index 0489e132f..0b378e53c 100644
--- a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DirectEventProcessorTest.java
+++ b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/DirectEventProcessorTest.java
@@ -3801,14 +3801,47 @@ public void goingOfflineStopsTheRetryOfAFailedPost() throws Exception {
}
@Test
- public void theDiagnosticInitEventWaitsForTheLaunchGateOnlyWhereEventsPersist() throws Exception {
+ public void theDiagnosticInitEventWaitsForTheLaunchGateWhetherOrNotEventsPersist() throws Exception {
try (HttpServer server = startEventsServer()) {
EventProcessor eventProcessor = nextRunOffline(server, new DirectEventProcessor.Pacing(1_000, 0), false);
try {
eventProcessor.setOffline(false);
- if (persistence() != EventPersistence.DISABLED) {
- server.getRecorder().requireNoRequests(600, TimeUnit.MILLISECONDS);
- }
+ server.getRecorder().requireNoRequests(600, TimeUnit.MILLISECONDS);
+
+ assertEquals("/mobile/events/diagnostic",
+ server.getRecorder().requireRequest(2, TimeUnit.SECONDS).getPath());
+ } finally {
+ eventProcessor.close();
+ }
+ }
+ }
+
+ @Test
+ public void theDiagnosticInitEventGoesOutAtOnceWithNoLaunchDelay() throws Exception {
+ try (HttpServer server = startEventsServer()) {
+ EventProcessor eventProcessor = nextRunOffline(server, DirectEventProcessor.Pacing.NONE, false);
+ try {
+ eventProcessor.setOffline(false);
+
+ assertEquals("/mobile/events/diagnostic",
+ server.getRecorder().requireRequest(500, TimeUnit.MILLISECONDS).getPath());
+ } finally {
+ eventProcessor.close();
+ }
+ }
+ }
+
+ @Test
+ public void theLaunchDelayIsTheConfiguredEventDeliveryDelay() throws Exception {
+ try (HttpServer server = startEventsServer()) {
+ EventProcessor eventProcessor = buildOfflineEventProcessor(server,
+ ((ComponentsImpl.EventProcessorBuilderImpl) eventsBuilder(DEFAULT_CAPACITY)
+ .eventDeliveryDelayMillis(1_000))
+ .pacingForTesting(null),
+ false);
+ try {
+ eventProcessor.setOffline(false);
+ server.getRecorder().requireNoRequests(600, TimeUnit.MILLISECONDS);
assertEquals("/mobile/events/diagnostic",
server.getRecorder().requireRequest(2, TimeUnit.SECONDS).getPath());
diff --git a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/integrations/EventProcessorBuilderTest.java b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/integrations/EventProcessorBuilderTest.java
new file mode 100644
index 000000000..6c5ec6837
--- /dev/null
+++ b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/integrations/EventProcessorBuilderTest.java
@@ -0,0 +1,24 @@
+package com.launchdarkly.sdk.android.integrations;
+
+import com.launchdarkly.sdk.android.Components;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class EventProcessorBuilderTest {
+ @Test
+ public void eventDeliveryDelayDefaultsToTwoSeconds() {
+ assertEquals(2_000, Components.sendEvents().eventDeliveryDelayMillis);
+ }
+
+ @Test
+ public void eventDeliveryDelayIsKeptBetweenZeroAndAMinute() {
+ EventProcessorBuilder builder = Components.sendEvents();
+ assertEquals(0, builder.eventDeliveryDelayMillis(0).eventDeliveryDelayMillis);
+ assertEquals(5_000, builder.eventDeliveryDelayMillis(5_000).eventDeliveryDelayMillis);
+ assertEquals(0, builder.eventDeliveryDelayMillis(-1).eventDeliveryDelayMillis);
+ assertEquals(60_000, builder.eventDeliveryDelayMillis(61_000).eventDeliveryDelayMillis);
+ assertEquals(60_000, builder.eventDeliveryDelayMillis(Long.MAX_VALUE).eventDeliveryDelayMillis);
+ }
+}