Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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. */
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ public abstract class EventProcessorBuilder implements ComponentConfigurer<Event
*/
public static final long DEFAULT_MAX_AGE_MILLIS = 7L * 24 * 60 * 60 * 1000;

/**
* The default value for {@link #eventDeliveryDelayMillis(long)}: 2 seconds.
*/
public static final long DEFAULT_EVENT_DELIVERY_DELAY_MILLIS = 2_000;

/**
* The maximum value for {@link #eventDeliveryDelayMillis(long)}: 60 seconds.
*/
public static final long MAX_EVENT_DELIVERY_DELAY_MILLIS = 60_000;

/**
* All attributes should be treated as private
*/
Expand Down Expand Up @@ -96,6 +106,11 @@ public abstract class EventProcessorBuilder implements ComponentConfigurer<Event
*/
protected long maxAgeMillis = DEFAULT_MAX_AGE_MILLIS;

/**
* How long after the SDK first goes online the traffic it starts on its own waits, in millis
*/
protected long eventDeliveryDelayMillis = DEFAULT_EVENT_DELIVERY_DELAY_MILLIS;

/**
* Set of attributes by reference that will be treated as private
*/
Expand Down Expand Up @@ -235,6 +250,30 @@ public EventProcessorBuilder maxAgeMillis(long maxAgeMillis) {
return this;
}

/**
* Sets how long after the SDK first goes online it holds back the traffic it starts on its own, so that
* the first flag request has the network to itself.
* <p>
* That is the diagnostic init event, the events a previous run left where
* {@link #eventPersistence(EventPersistence)} keeps them, and the periodic delivery of this run's events.
* A flush the application asks for is never held. Zero sends them as soon as the SDK is online.
* <p>
* This setting is experimental, and not subject to any backwards compatibility guarantees or semantic
* versioning.
* <p>
* 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.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading