From 244e04b20e33c70289578394ba97f38f290dee6b Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Thu, 3 Sep 2026 16:59:53 -0700 Subject: [PATCH 01/10] feat(events): summarize and buffer events on the recording thread (tier 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tier 1 of the event durability spec: the buffer. AndroidEventBuffer folds an evaluation into summary counters as it is recorded and holds in full only the events that have to be sent one by one, so a burst of evaluations of untracked flags cannot displace anything. AndroidEventProcessor takes over from java-sdk-internal's DefaultEventProcessor in ComponentsImpl, removing the bounded queue that used to sit between the calling thread and the summarizer. Spec: Event Durability, "Tier 1 — the buffer". Co-authored-by: Cursor --- .../launchdarkly/example/MainActivity.java | 24 ++ example/src/main/res/layout/activity_main.xml | 13 +- launchdarkly-android-client-sdk/build.gradle | 12 + .../sdk/android/AndroidEventProcessor.java | 359 ++++++++++++++++++ .../sdk/android/ComponentsImpl.java | 109 +----- .../internal/events/AndroidEventBuffer.java | 189 +++++++++ .../android/AndroidEventProcessorTest.java | 252 ++++++++++++ .../android/EventProcessorBufferingTest.java | 161 ++++++++ .../android/EventProcessorPrivacyTest.java | 269 +++++++++++++ .../sdk/android/EventProcessorTestBase.java | 145 +++++++ 10 files changed, 1442 insertions(+), 91 deletions(-) create mode 100644 launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/AndroidEventProcessor.java create mode 100644 launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/internal/events/AndroidEventBuffer.java create mode 100644 launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/AndroidEventProcessorTest.java create mode 100644 launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/EventProcessorBufferingTest.java create mode 100644 launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/EventProcessorPrivacyTest.java create mode 100644 launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/EventProcessorTestBase.java diff --git a/example/src/main/java/com/launchdarkly/example/MainActivity.java b/example/src/main/java/com/launchdarkly/example/MainActivity.java index 9b604324..e7deb6ca 100644 --- a/example/src/main/java/com/launchdarkly/example/MainActivity.java +++ b/example/src/main/java/com/launchdarkly/example/MainActivity.java @@ -102,6 +102,7 @@ public void onCreate(Bundle savedInstanceState) { setupFlushButton(); setupTrackButton(); setupIdentifyButton(); + setupKillUnsentButton(); setupOfflineSwitch(); setupListeners(); updateDedupeStatus(); @@ -215,6 +216,29 @@ private void setupTrackButton() { }); } + /** + * Reproduces in-memory event loss: evaluate (exposure) and track (stand-in for an error), + * wait 5s so both calls are queued, then kill the process before the 30s flush. + * {@code finish()} or backgrounding would run the SDK's background flush, so this uses + * {@link android.os.Process#killProcess}. + */ + private void setupKillUnsentButton() { + Button killUnsentButton = findViewById(R.id.kill_unsent_button); + killUnsentButton.setOnClickListener(v -> { + final String typedKey = ((EditText) findViewById(R.id.feature_flag_key)).getText().toString().trim(); + final String flagKey = typedKey.isEmpty() ? "kill-flag" : typedKey; + Timber.w("eval+track+kill flag=%s", flagKey); + doSafeClientAction(() -> { + ldClient.boolVariation(flagKey, false); + ldClient.track("$ld:telemetry:error"); + ldClient.flush(); + new Handler(Looper.getMainLooper()).postDelayed( + () -> android.os.Process.killProcess(android.os.Process.myPid()), + 5_000); + }); + }); + } + private void setupIdentifyButton() { Button identify = findViewById(R.id.identify_button); identify.setOnClickListener(v -> { diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml index 8657d607..06c5edb9 100644 --- a/example/src/main/res/layout/activity_main.xml +++ b/example/src/main/res/layout/activity_main.xml @@ -114,6 +114,17 @@ android:layout_alignParentRight="true" android:minLines="4" /> +