diff --git a/api/.gitignore b/api/.gitignore index 796b96d1c..e4dbec6f2 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -1 +1,3 @@ /build +.classpath +.settings diff --git a/backoff/.gitignore b/backoff/.gitignore index 3a11ced48..6009265cd 100644 --- a/backoff/.gitignore +++ b/backoff/.gitignore @@ -2,3 +2,5 @@ .gradle *.iml .DS_Store +.classpath +.settings diff --git a/build.gradle b/build.gradle index 1abeacb15..91abd934d 100644 --- a/build.gradle +++ b/build.gradle @@ -145,7 +145,7 @@ dependencies { return candidates.find { findProject(it) != null } } - ['main', 'logger', 'events', 'events-domain', 'api', 'http-api', 'http', 'fallback', 'backoff', 'tracker'].each { moduleName -> + ['main', 'logger', 'executor', 'events', 'events-domain', 'api', 'http-api', 'http', 'fallback', 'backoff', 'tracker', 'submitter'].each { moduleName -> def resolvedPath = resolveProjectPath(moduleName) if (resolvedPath != null) { include project(resolvedPath) diff --git a/events-domain/.gitignore b/events-domain/.gitignore index 796b96d1c..e4dbec6f2 100644 --- a/events-domain/.gitignore +++ b/events-domain/.gitignore @@ -1 +1,3 @@ /build +.classpath +.settings diff --git a/events-domain/build.gradle b/events-domain/build.gradle index 4b4d0941b..cce729dd2 100644 --- a/events-domain/build.gradle +++ b/events-domain/build.gradle @@ -16,6 +16,7 @@ android { dependencies { implementation libs.annotation + api clientModuleProject('executor') implementation clientModuleProject('api') implementation clientModuleProject('events') implementation clientModuleProject('logger') diff --git a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskType.java b/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskType.java deleted file mode 100644 index 7842fea6d..000000000 --- a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskType.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.split.android.client.service.executor; - -public enum SplitTaskType { - SPLITS_SYNC, MY_SEGMENTS_SYNC, EVENTS_RECORDER, IMPRESSIONS_RECORDER, - LOAD_LOCAL_SPLITS, LOAD_LOCAL_MY_SEGMENTS, SSE_AUTHENTICATION_TASK, - SPLIT_KILL, FILTER_SPLITS_CACHE, GENERIC_TASK, - CLEAN_UP_DATABASE, IMPRESSIONS_COUNT_RECORDER, SAVE_IMPRESSIONS_COUNT, - MY_SEGMENTS_UPDATE, LOAD_LOCAL_ATTRIBUTES, - TELEMETRY_CONFIG_TASK, TELEMETRY_STATS_TASK, - SAVE_UNIQUE_KEYS_TASK, UNIQUE_KEYS_RECORDER_TASK, - MY_LARGE_SEGMENTS_UPDATE, LOAD_LOCAL_RULE_BASED_SEGMENTS, - RULE_BASED_SEGMENT_SYNC, -} diff --git a/events/.gitignore b/events/.gitignore index 42afabfd2..0b60b6351 100644 --- a/events/.gitignore +++ b/events/.gitignore @@ -1 +1,3 @@ -/build \ No newline at end of file +/build +.classpath +.settings \ No newline at end of file diff --git a/executor/.gitignore b/executor/.gitignore new file mode 100644 index 000000000..22e7e7b81 --- /dev/null +++ b/executor/.gitignore @@ -0,0 +1,5 @@ +/build +.gradle +local.properties +.classpath +.settings \ No newline at end of file diff --git a/executor/README.md b/executor/README.md new file mode 100644 index 000000000..e07ec571a --- /dev/null +++ b/executor/README.md @@ -0,0 +1,60 @@ +# executor + +Generic task scheduling and execution infrastructure for the Split Android SDK. + +## Purpose + +Provides a pausable task executor with support for scheduled and immediate task execution, parallel task execution with timeout, serial and batch task wrappers, main thread task execution via Android Handler, and pause/resume/stop controls. + +## Usage + +### Basic Task Execution + +```java +SplitTaskExecutor executor = new SplitTaskExecutorImpl(); + +SplitTask task = () -> { + // Do work + return SplitTaskExecutionInfo.success(SplitTaskType.GENERIC_TASK); +}; + +executor.submit(task, null); +``` + +### Scheduled Execution + +```java +executor.schedule( + task, + 60, // delay in seconds + null // optional listener +); +``` + +### Parallel Execution + +```java +SplitParallelTaskExecutor parallelExecutor = + new SplitParallelTaskExecutorFactoryImpl().build(); + +List> tasks = Arrays.asList( + () -> fetchData1(), + () -> fetchData2() +); + +List results = parallelExecutor.executeParallelTasks(tasks, 60); +``` + +### Lifecycle Management + +```java +executor.pause(); // Pause scheduled tasks +executor.resume(); // Resume scheduled tasks +executor.stop(); // Stop and shutdown executor +``` + +## Dependencies + +- **logger**: Logging abstraction +- **Android framework**: Handler/Looper for main thread execution +- **AndroidX annotations**: @NonNull, @Nullable, etc. diff --git a/executor/build.gradle b/executor/build.gradle new file mode 100644 index 000000000..17b2228c4 --- /dev/null +++ b/executor/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'com.android.library' +} + +apply from: "$projectDir/../gradle/common-android-library.gradle" + +android { + namespace 'io.split.android.client.executor' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation libs.annotation + implementation clientModuleProject('logger') + + testImplementation libs.junit4 + testImplementation libs.mockitoCore +} diff --git a/main/src/main/java/io/split/android/client/service/executor/SplitBaseTaskExecutor.java b/executor/src/main/java/io/split/android/client/service/executor/SplitBaseTaskExecutor.java similarity index 94% rename from main/src/main/java/io/split/android/client/service/executor/SplitBaseTaskExecutor.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitBaseTaskExecutor.java index 997cfbbab..e63dfb4ed 100644 --- a/main/src/main/java/io/split/android/client/service/executor/SplitBaseTaskExecutor.java +++ b/executor/src/main/java/io/split/android/client/service/executor/SplitBaseTaskExecutor.java @@ -1,8 +1,5 @@ package io.split.android.client.service.executor; -import static io.split.android.client.utils.Utils.checkArgument; -import static io.split.android.client.utils.Utils.checkNotNull; - import android.os.Handler; import android.os.Looper; @@ -12,6 +9,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; @@ -42,8 +40,10 @@ public String schedule(@NonNull SplitTask task, long periodInSecs, @Nullable SplitTaskExecutionListener executionListener ) { - checkNotNull(task); - checkArgument(periodInSecs > 0); + Objects.requireNonNull(task); + if (periodInSecs <= 0) { + throw new IllegalArgumentException("periodInSecs must be positive"); + } String taskId = null; if (!mScheduler.isShutdown()) { @@ -62,7 +62,7 @@ public String schedule(@NonNull SplitTask task, long initialDelayInSecs, @Nullable SplitTaskExecutionListener executionListener ) { - checkNotNull(task); + Objects.requireNonNull(task); String taskId = null; if (!mScheduler.isShutdown()) { ScheduledFuture taskFuture = mScheduler.schedule( @@ -77,7 +77,7 @@ public String schedule(@NonNull SplitTask task, @Override public void submit(@NonNull SplitTask task, @Nullable SplitTaskExecutionListener executionListener) { - checkNotNull(task); + Objects.requireNonNull(task); if (!mScheduler.isShutdown()) { mScheduler.submit(new TaskWrapper(task, executionListener)); } diff --git a/main/src/main/java/io/split/android/client/service/executor/SplitClientEventTaskExecutor.java b/executor/src/main/java/io/split/android/client/service/executor/SplitClientEventTaskExecutor.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/SplitClientEventTaskExecutor.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitClientEventTaskExecutor.java diff --git a/main/src/main/java/io/split/android/client/service/executor/SplitSingleThreadTaskExecutor.java b/executor/src/main/java/io/split/android/client/service/executor/SplitSingleThreadTaskExecutor.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/SplitSingleThreadTaskExecutor.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitSingleThreadTaskExecutor.java diff --git a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTask.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTask.java similarity index 100% rename from events-domain/src/main/java/io/split/android/client/service/executor/SplitTask.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTask.java diff --git a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskBatchItem.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskBatchItem.java similarity index 100% rename from events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskBatchItem.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskBatchItem.java diff --git a/main/src/main/java/io/split/android/client/service/executor/SplitTaskBatchWrapper.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskBatchWrapper.java similarity index 88% rename from main/src/main/java/io/split/android/client/service/executor/SplitTaskBatchWrapper.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskBatchWrapper.java index 47b0dca6b..0e41e1165 100644 --- a/main/src/main/java/io/split/android/client/service/executor/SplitTaskBatchWrapper.java +++ b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskBatchWrapper.java @@ -1,8 +1,7 @@ package io.split.android.client.service.executor; -import static io.split.android.client.utils.Utils.checkNotNull; - import java.util.List; +import java.util.Objects; import io.split.android.client.utils.logger.Logger; @@ -10,7 +9,7 @@ class SplitTaskBatchWrapper implements Runnable { private final List mTaskQueue; SplitTaskBatchWrapper(List taskQueue) { - mTaskQueue = checkNotNull(taskQueue); + mTaskQueue = Objects.requireNonNull(taskQueue); } @Override diff --git a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java similarity index 100% rename from events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionInfo.java diff --git a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionListener.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionListener.java similarity index 100% rename from events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionListener.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionListener.java diff --git a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionStatus.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionStatus.java similarity index 100% rename from events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionStatus.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutionStatus.java diff --git a/events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutor.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutor.java similarity index 100% rename from events-domain/src/main/java/io/split/android/client/service/executor/SplitTaskExecutor.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutor.java diff --git a/main/src/main/java/io/split/android/client/service/executor/SplitTaskExecutorImpl.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutorImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/SplitTaskExecutorImpl.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskExecutorImpl.java diff --git a/main/src/main/java/io/split/android/client/service/executor/SplitTaskSerialWrapper.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskSerialWrapper.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/SplitTaskSerialWrapper.java rename to executor/src/main/java/io/split/android/client/service/executor/SplitTaskSerialWrapper.java diff --git a/executor/src/main/java/io/split/android/client/service/executor/SplitTaskType.java b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskType.java new file mode 100644 index 000000000..8eaacb000 --- /dev/null +++ b/executor/src/main/java/io/split/android/client/service/executor/SplitTaskType.java @@ -0,0 +1,10 @@ +package io.split.android.client.service.executor; + +public interface SplitTaskType { + SplitTaskType GENERIC_TASK = new SplitTaskType() { + @Override + public String toString() { + return "GENERIC_TASK"; + } + }; +} diff --git a/main/src/main/java/io/split/android/client/service/executor/TaskWrapper.java b/executor/src/main/java/io/split/android/client/service/executor/TaskWrapper.java similarity index 89% rename from main/src/main/java/io/split/android/client/service/executor/TaskWrapper.java rename to executor/src/main/java/io/split/android/client/service/executor/TaskWrapper.java index 71018e3ef..f5009a33a 100644 --- a/main/src/main/java/io/split/android/client/service/executor/TaskWrapper.java +++ b/executor/src/main/java/io/split/android/client/service/executor/TaskWrapper.java @@ -1,8 +1,7 @@ package io.split.android.client.service.executor; -import static io.split.android.client.utils.Utils.checkNotNull; - import java.lang.ref.WeakReference; +import java.util.Objects; import io.split.android.client.utils.logger.Logger; @@ -12,7 +11,7 @@ class TaskWrapper implements Runnable { TaskWrapper(SplitTask task, SplitTaskExecutionListener executionListener) { - mTask = checkNotNull(task); + mTask = Objects.requireNonNull(task); mExecutionListener = new WeakReference<>(executionListener); } diff --git a/main/src/main/java/io/split/android/client/service/executor/ThreadFactoryBuilder.java b/executor/src/main/java/io/split/android/client/service/executor/ThreadFactoryBuilder.java similarity index 93% rename from main/src/main/java/io/split/android/client/service/executor/ThreadFactoryBuilder.java rename to executor/src/main/java/io/split/android/client/service/executor/ThreadFactoryBuilder.java index 4b3059b48..adb4000e7 100644 --- a/main/src/main/java/io/split/android/client/service/executor/ThreadFactoryBuilder.java +++ b/executor/src/main/java/io/split/android/client/service/executor/ThreadFactoryBuilder.java @@ -2,9 +2,6 @@ import static java.util.Objects.requireNonNull; -import static io.split.android.client.utils.Utils.checkArgument; -import static io.split.android.client.utils.Utils.checkNotNull; - import androidx.annotation.Nullable; import java.util.Locale; @@ -71,8 +68,12 @@ public ThreadFactoryBuilder setDaemon(boolean daemon) { public ThreadFactoryBuilder setPriority(int priority) { // Thread#setPriority() already checks for validity. These error messages // are nicer though and will fail-fast. - checkArgument(priority >= Thread.MIN_PRIORITY); - checkArgument(priority <= Thread.MAX_PRIORITY); + if (priority < Thread.MIN_PRIORITY) { + throw new IllegalArgumentException("priority must be >= Thread.MIN_PRIORITY"); + } + if (priority > Thread.MAX_PRIORITY) { + throw new IllegalArgumentException("priority must be <= Thread.MAX_PRIORITY"); + } this.priority = priority; return this; } @@ -86,7 +87,7 @@ public ThreadFactoryBuilder setPriority(int priority) { */ public ThreadFactoryBuilder setUncaughtExceptionHandler( Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { - this.uncaughtExceptionHandler = checkNotNull(uncaughtExceptionHandler); + this.uncaughtExceptionHandler = requireNonNull(uncaughtExceptionHandler); return this; } @@ -99,7 +100,7 @@ public ThreadFactoryBuilder setUncaughtExceptionHandler( * @return this for the builder pattern */ public ThreadFactoryBuilder setThreadFactory(ThreadFactory backingThreadFactory) { - this.backingThreadFactory = checkNotNull(backingThreadFactory); + this.backingThreadFactory = requireNonNull(backingThreadFactory); return this; } diff --git a/main/src/main/java/io/split/android/client/service/executor/parallel/SplitDeferredTaskItem.java b/executor/src/main/java/io/split/android/client/service/executor/parallel/SplitDeferredTaskItem.java similarity index 78% rename from main/src/main/java/io/split/android/client/service/executor/parallel/SplitDeferredTaskItem.java rename to executor/src/main/java/io/split/android/client/service/executor/parallel/SplitDeferredTaskItem.java index d905ca301..e32d0584f 100644 --- a/main/src/main/java/io/split/android/client/service/executor/parallel/SplitDeferredTaskItem.java +++ b/executor/src/main/java/io/split/android/client/service/executor/parallel/SplitDeferredTaskItem.java @@ -1,9 +1,8 @@ package io.split.android.client.service.executor.parallel; -import static io.split.android.client.utils.Utils.checkNotNull; - import androidx.annotation.NonNull; +import java.util.Objects; import java.util.concurrent.Callable; public class SplitDeferredTaskItem implements Callable { @@ -11,7 +10,7 @@ public class SplitDeferredTaskItem implements Callable { private final Callable mCallable; public SplitDeferredTaskItem(@NonNull Callable callable) { - mCallable = checkNotNull(callable); + mCallable = Objects.requireNonNull(callable); } @Override diff --git a/main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutor.java b/executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutor.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutor.java rename to executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutor.java diff --git a/main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactory.java b/executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactory.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactory.java rename to executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactory.java diff --git a/main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactoryImpl.java b/executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactoryImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactoryImpl.java rename to executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorFactoryImpl.java diff --git a/main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImpl.java b/executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImpl.java rename to executor/src/main/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImpl.java diff --git a/main/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutor.java b/executor/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutor.java similarity index 100% rename from main/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutor.java rename to executor/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutor.java diff --git a/main/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImpl.java b/executor/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImpl.java similarity index 100% rename from main/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImpl.java rename to executor/src/main/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImpl.java diff --git a/events-domain/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutor.java b/executor/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutor.java similarity index 100% rename from events-domain/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutor.java rename to executor/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutor.java diff --git a/events-domain/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutorImpl.java b/executor/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutorImpl.java similarity index 100% rename from events-domain/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutorImpl.java rename to executor/src/main/java/io/split/android/engine/scheduler/PausableThreadPoolExecutorImpl.java diff --git a/main/src/test/java/io/split/android/client/service/SplitTaskExecutorTest.java b/executor/src/test/java/io/split/android/client/service/SplitTaskExecutorTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/service/SplitTaskExecutorTest.java rename to executor/src/test/java/io/split/android/client/service/SplitTaskExecutorTest.java diff --git a/main/src/test/java/io/split/android/client/service/executor/SplitTaskSerialWrapperTest.java b/executor/src/test/java/io/split/android/client/service/executor/SplitTaskSerialWrapperTest.java similarity index 80% rename from main/src/test/java/io/split/android/client/service/executor/SplitTaskSerialWrapperTest.java rename to executor/src/test/java/io/split/android/client/service/executor/SplitTaskSerialWrapperTest.java index 11dee9e9a..4b47a9b81 100644 --- a/main/src/test/java/io/split/android/client/service/executor/SplitTaskSerialWrapperTest.java +++ b/executor/src/test/java/io/split/android/client/service/executor/SplitTaskSerialWrapperTest.java @@ -12,13 +12,17 @@ public class SplitTaskSerialWrapperTest { + private static final SplitTaskType TASK_TYPE_A = new SplitTaskType() {}; + private static final SplitTaskType TASK_TYPE_B = new SplitTaskType() {}; + private static final SplitTaskType TASK_TYPE_C = new SplitTaskType() {}; + @Test public void successfulStatusContainsResultsOfEveryTask() { SplitTask task1 = mock(SplitTask.class); SplitTask task2 = mock(SplitTask.class); - when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.TELEMETRY_CONFIG_TASK)); - when(task2.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.TELEMETRY_STATS_TASK)); + when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_A)); + when(task2.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_B)); SplitTaskSerialWrapper wrapper = new SplitTaskSerialWrapper(task1, task2); @@ -27,10 +31,10 @@ public void successfulStatusContainsResultsOfEveryTask() { List results = (List) executionInfo.getObjectValue("serial_task_results"); assertEquals(SplitTaskExecutionStatus.SUCCESS, executionInfo.getStatus()); assertEquals(2, results.size()); - assertEquals(SplitTaskType.TELEMETRY_CONFIG_TASK, results.get(0).getTaskType()); + assertEquals(TASK_TYPE_A, results.get(0).getTaskType()); assertEquals(SplitTaskExecutionStatus.SUCCESS, results.get(0).getStatus()); - assertEquals(SplitTaskType.TELEMETRY_STATS_TASK, results.get(1).getTaskType()); + assertEquals(TASK_TYPE_B, results.get(1).getTaskType()); assertEquals(SplitTaskExecutionStatus.SUCCESS, results.get(1).getStatus()); } @@ -40,9 +44,9 @@ public void unsuccessfulResultContainsExecutionInfoUpToFirstUnsuccessfulTask() { SplitTask task2 = mock(SplitTask.class); SplitTask task3 = mock(SplitTask.class); - when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.TELEMETRY_CONFIG_TASK)); - when(task2.execute()).thenReturn(SplitTaskExecutionInfo.error(SplitTaskType.TELEMETRY_STATS_TASK)); - when(task3.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_RECORDER)); + when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_A)); + when(task2.execute()).thenReturn(SplitTaskExecutionInfo.error(TASK_TYPE_B)); + when(task3.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_C)); SplitTaskSerialWrapper wrapper = new SplitTaskSerialWrapper(task1, task2, task3); @@ -51,10 +55,10 @@ public void unsuccessfulResultContainsExecutionInfoUpToFirstUnsuccessfulTask() { List results = (List) executionInfo.getObjectValue("serial_task_results"); assertEquals(SplitTaskExecutionStatus.ERROR, executionInfo.getStatus()); assertEquals(2, results.size()); - assertEquals(SplitTaskType.TELEMETRY_CONFIG_TASK, results.get(0).getTaskType()); + assertEquals(TASK_TYPE_A, results.get(0).getTaskType()); assertEquals(SplitTaskExecutionStatus.SUCCESS, results.get(0).getStatus()); - assertEquals(SplitTaskType.TELEMETRY_STATS_TASK, results.get(1).getTaskType()); + assertEquals(TASK_TYPE_B, results.get(1).getTaskType()); assertEquals(SplitTaskExecutionStatus.ERROR, results.get(1).getStatus()); } @@ -64,9 +68,9 @@ public void successfulTasksAreAllExecuted() { SplitTask task2 = mock(SplitTask.class); SplitTask task3 = mock(SplitTask.class); - when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.TELEMETRY_CONFIG_TASK)); - when(task2.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.TELEMETRY_STATS_TASK)); - when(task3.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_RECORDER)); + when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_A)); + when(task2.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_B)); + when(task3.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_C)); SplitTaskSerialWrapper wrapper = new SplitTaskSerialWrapper(task1, task2, task3); @@ -84,10 +88,10 @@ public void tasksAreExecutedUpToUnsuccessfulOne() { SplitTask task3 = mock(SplitTask.class); SplitTask task4 = mock(SplitTask.class); - when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.TELEMETRY_CONFIG_TASK)); - when(task2.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.TELEMETRY_STATS_TASK)); - when(task3.execute()).thenReturn(SplitTaskExecutionInfo.error(SplitTaskType.IMPRESSIONS_RECORDER)); - when(task4.execute()).thenReturn(SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_RECORDER)); + when(task1.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_A)); + when(task2.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_B)); + when(task3.execute()).thenReturn(SplitTaskExecutionInfo.error(TASK_TYPE_C)); + when(task4.execute()).thenReturn(SplitTaskExecutionInfo.success(TASK_TYPE_C)); SplitTaskSerialWrapper wrapper = new SplitTaskSerialWrapper(task1, task2, task3, task4); wrapper.execute(); diff --git a/main/src/test/java/io/split/android/client/service/executor/ThreadFactoryBuilderTest.java b/executor/src/test/java/io/split/android/client/service/executor/ThreadFactoryBuilderTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/service/executor/ThreadFactoryBuilderTest.java rename to executor/src/test/java/io/split/android/client/service/executor/ThreadFactoryBuilderTest.java diff --git a/main/src/test/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImplTest.java b/executor/src/test/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImplTest.java rename to executor/src/test/java/io/split/android/client/service/executor/parallel/SplitParallelTaskExecutorImplTest.java diff --git a/main/src/test/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImplTest.java b/executor/src/test/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImplTest.java rename to executor/src/test/java/io/split/android/engine/scheduler/PausableScheduledThreadPoolExecutorImplTest.java diff --git a/fallback/.gitignore b/fallback/.gitignore index 3a11ced48..6009265cd 100644 --- a/fallback/.gitignore +++ b/fallback/.gitignore @@ -2,3 +2,5 @@ .gradle *.iml .DS_Store +.classpath +.settings diff --git a/gradle/common-android-library.gradle b/gradle/common-android-library.gradle index 28bfe3eee..adf1d647d 100644 --- a/gradle/common-android-library.gradle +++ b/gradle/common-android-library.gradle @@ -14,6 +14,10 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs.add('-parameters') } +tasks.withType(Test).configureEach { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 +} + def kotlinCompileClass = null try { kotlinCompileClass = Class.forName('org.jetbrains.kotlin.gradle.tasks.KotlinCompile') diff --git a/http-api/.gitignore b/http-api/.gitignore index 796b96d1c..e4dbec6f2 100644 --- a/http-api/.gitignore +++ b/http-api/.gitignore @@ -1 +1,3 @@ /build +.classpath +.settings diff --git a/http/.gitignore b/http/.gitignore index 796b96d1c..e4dbec6f2 100644 --- a/http/.gitignore +++ b/http/.gitignore @@ -1 +1,3 @@ /build +.classpath +.settings diff --git a/logger/.gitignore b/logger/.gitignore index 3a11ced48..6009265cd 100644 --- a/logger/.gitignore +++ b/logger/.gitignore @@ -2,3 +2,5 @@ .gradle *.iml .DS_Store +.classpath +.settings diff --git a/main/.gitignore b/main/.gitignore index 3a11ced48..6009265cd 100644 --- a/main/.gitignore +++ b/main/.gitignore @@ -2,3 +2,5 @@ .gradle *.iml .DS_Store +.classpath +.settings diff --git a/main/build.gradle b/main/build.gradle index a0325264f..8029dd92e 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -50,12 +50,15 @@ android { dependencies { // Public api modules + api clientModuleProject('executor') api clientModuleProject('logger') api clientModuleProject('api') api clientModuleProject('http-api') api clientModuleProject('fallback') implementation clientModuleProject('backoff') implementation clientModuleProject('tracker') + api clientModuleProject('submitter') + // Internal module dependencies implementation clientModuleProject('http') implementation clientModuleProject('events-domain') diff --git a/main/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java b/main/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java index 8f3539423..b2865386f 100644 --- a/main/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java +++ b/main/src/androidTest/java/tests/workmanager/WorkManagerWrapperTest.java @@ -34,7 +34,7 @@ import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFilter; import io.split.android.client.network.CertificatePinningConfiguration; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.synchronizer.WorkManagerWrapper; import io.split.android.client.service.workmanager.EventsRecorderWorker; import io.split.android.client.service.workmanager.ImpressionsRecorderWorker; diff --git a/main/src/main/java/io/split/android/client/dtos/Event.java b/main/src/main/java/io/split/android/client/dtos/Event.java index fe8c986d9..d10397363 100644 --- a/main/src/main/java/io/split/android/client/dtos/Event.java +++ b/main/src/main/java/io/split/android/client/dtos/Event.java @@ -3,7 +3,7 @@ import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; -import io.split.android.client.storage.common.InBytesSizable; +import io.split.android.client.submitter.InBytesSizable; import io.split.android.client.utils.deserializer.EventDeserializer; @JsonAdapter(EventDeserializer.class) diff --git a/main/src/main/java/io/split/android/client/dtos/KeyImpression.java b/main/src/main/java/io/split/android/client/dtos/KeyImpression.java index 8bf7f2e7e..6cd3795e8 100644 --- a/main/src/main/java/io/split/android/client/dtos/KeyImpression.java +++ b/main/src/main/java/io/split/android/client/dtos/KeyImpression.java @@ -6,7 +6,7 @@ import java.util.Objects; import io.split.android.client.service.ServiceConstants; -import io.split.android.client.storage.common.InBytesSizable; +import io.split.android.client.submitter.InBytesSizable; import io.split.android.client.impressions.Impression; public class KeyImpression implements InBytesSizable, Identifiable { diff --git a/main/src/main/java/io/split/android/client/service/CleanUpDatabaseTask.java b/main/src/main/java/io/split/android/client/service/CleanUpDatabaseTask.java index 1eca9ba92..a487d9ef4 100644 --- a/main/src/main/java/io/split/android/client/service/CleanUpDatabaseTask.java +++ b/main/src/main/java/io/split/android/client/service/CleanUpDatabaseTask.java @@ -4,7 +4,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.impressions.observer.PersistentImpressionsObserverCacheStorage; import io.split.android.client.storage.events.PersistentEventsStorage; import io.split.android.client.storage.impressions.PersistentImpressionsCountStorage; diff --git a/main/src/main/java/io/split/android/client/service/HttpRecorderSubmitterAdapter.java b/main/src/main/java/io/split/android/client/service/HttpRecorderSubmitterAdapter.java new file mode 100644 index 000000000..beb51fa7b --- /dev/null +++ b/main/src/main/java/io/split/android/client/service/HttpRecorderSubmitterAdapter.java @@ -0,0 +1,28 @@ +package io.split.android.client.service; + +import androidx.annotation.NonNull; + +import io.split.android.client.service.http.HttpRecorder; +import io.split.android.client.service.http.HttpRecorderException; +import io.split.android.client.service.http.HttpStatus; +import io.split.android.client.submitter.RecorderException; +import io.split.android.client.submitter.RecorderSubmitter; + +public class HttpRecorderSubmitterAdapter implements RecorderSubmitter { + private final HttpRecorder mHttpRecorder; + + public HttpRecorderSubmitterAdapter(@NonNull HttpRecorder httpRecorder) { + mHttpRecorder = httpRecorder; + } + + @Override + public void execute(@NonNull T data) throws RecorderException { + try { + mHttpRecorder.execute(data); + } catch (HttpRecorderException e) { + Integer httpStatus = e.getHttpStatus(); + boolean retryable = !HttpStatus.isNotRetryable(HttpStatus.fromCode(httpStatus)); + throw new RecorderException(e.getMessage(), httpStatus, retryable); + } + } +} diff --git a/main/src/main/java/io/split/android/client/service/SplitTaskType.java b/main/src/main/java/io/split/android/client/service/SplitTaskType.java new file mode 100644 index 000000000..0ee0d016e --- /dev/null +++ b/main/src/main/java/io/split/android/client/service/SplitTaskType.java @@ -0,0 +1,25 @@ +package io.split.android.client.service; + +public enum SplitTaskType implements io.split.android.client.service.executor.SplitTaskType { + SPLITS_SYNC, + MY_SEGMENTS_SYNC, + EVENTS_RECORDER, + IMPRESSIONS_RECORDER, + LOAD_LOCAL_SPLITS, + LOAD_LOCAL_MY_SEGMENTS, + SSE_AUTHENTICATION_TASK, + SPLIT_KILL, + FILTER_SPLITS_CACHE, + CLEAN_UP_DATABASE, + IMPRESSIONS_COUNT_RECORDER, + SAVE_IMPRESSIONS_COUNT, + MY_SEGMENTS_UPDATE, + LOAD_LOCAL_ATTRIBUTES, + TELEMETRY_CONFIG_TASK, + TELEMETRY_STATS_TASK, + SAVE_UNIQUE_KEYS_TASK, + UNIQUE_KEYS_RECORDER_TASK, + MY_LARGE_SEGMENTS_UPDATE, + LOAD_LOCAL_RULE_BASED_SEGMENTS, + RULE_BASED_SEGMENT_SYNC, +} diff --git a/main/src/main/java/io/split/android/client/service/TelemetryRecorderAdapter.java b/main/src/main/java/io/split/android/client/service/TelemetryRecorderAdapter.java new file mode 100644 index 000000000..87610860f --- /dev/null +++ b/main/src/main/java/io/split/android/client/service/TelemetryRecorderAdapter.java @@ -0,0 +1,33 @@ +package io.split.android.client.service; + +import androidx.annotation.NonNull; + +import io.split.android.client.submitter.RecorderTelemetry; +import io.split.android.client.telemetry.model.OperationType; +import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; + +public class TelemetryRecorderAdapter implements RecorderTelemetry { + private final TelemetryRuntimeProducer mTelemetryProducer; + private final OperationType mOperationType; + + public TelemetryRecorderAdapter(@NonNull TelemetryRuntimeProducer telemetryProducer, + @NonNull OperationType operationType) { + mTelemetryProducer = telemetryProducer; + mOperationType = operationType; + } + + @Override + public void recordSuccess(long timestamp) { + mTelemetryProducer.recordSuccessfulSync(mOperationType, timestamp); + } + + @Override + public void recordError(Integer httpStatus) { + mTelemetryProducer.recordSyncError(mOperationType, httpStatus); + } + + @Override + public void recordLatency(long latencyMs) { + mTelemetryProducer.recordSyncLatency(mOperationType, latencyMs); + } +} diff --git a/main/src/main/java/io/split/android/client/service/attributes/LoadAttributesTask.java b/main/src/main/java/io/split/android/client/service/attributes/LoadAttributesTask.java index fd7883012..58cebeee2 100644 --- a/main/src/main/java/io/split/android/client/service/attributes/LoadAttributesTask.java +++ b/main/src/main/java/io/split/android/client/service/attributes/LoadAttributesTask.java @@ -7,7 +7,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.attributes.AttributesStorage; import io.split.android.client.storage.attributes.PersistentAttributesStorage; diff --git a/main/src/main/java/io/split/android/client/service/events/EventsRecorderTask.java b/main/src/main/java/io/split/android/client/service/events/EventsRecorderTask.java index d380af4e7..e26d121da 100644 --- a/main/src/main/java/io/split/android/client/service/events/EventsRecorderTask.java +++ b/main/src/main/java/io/split/android/client/service/events/EventsRecorderTask.java @@ -1,116 +1,42 @@ package io.split.android.client.service.events; -import static io.split.android.client.utils.Utils.checkNotNull; -import static io.split.android.client.utils.Utils.partition; - import androidx.annotation.NonNull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import io.split.android.client.dtos.Event; -import io.split.android.client.service.executor.SplitTask; -import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.HttpRecorderSubmitterAdapter; +import io.split.android.client.service.TelemetryRecorderAdapter; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; -import io.split.android.client.service.http.HttpRecorderException; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.storage.events.PersistentEventsStorage; +import io.split.android.client.submitter.RecorderTask; import io.split.android.client.telemetry.model.OperationType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; -import io.split.android.client.utils.logger.Logger; -public class EventsRecorderTask implements SplitTask { - public final static int FAILING_CHUNK_SIZE = 20; - private final PersistentEventsStorage mPersistentEventsStorage; - private final HttpRecorder> mHttpRecorder; - private final EventsRecorderTaskConfig mConfig; - private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; +public class EventsRecorderTask extends RecorderTask> { + + public static final int FAILING_CHUNK_SIZE = 20; public EventsRecorderTask(@NonNull HttpRecorder> httpRecorder, - @NonNull PersistentEventsStorage persistentEventsStorage, + @NonNull PersistentEventsStorage storage, @NonNull EventsRecorderTaskConfig config, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) { - mHttpRecorder = checkNotNull(httpRecorder); - mPersistentEventsStorage = checkNotNull(persistentEventsStorage); - mConfig = checkNotNull(config); - mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); + super(storage, + new HttpRecorderSubmitterAdapter<>(httpRecorder), + config.getEventsPerPush(), + SplitTaskType.EVENTS_RECORDER, + new TelemetryRecorderAdapter(telemetryRuntimeProducer, OperationType.EVENTS), + FAILING_CHUNK_SIZE); } @Override - @NonNull - public SplitTaskExecutionInfo execute() { - SplitTaskExecutionStatus status = SplitTaskExecutionStatus.SUCCESS; - int nonSentRecords = 0; - long nonSentBytes = 0; - List events; - List failingEvents = new ArrayList<>(); - boolean doNotRetry = false; - do { - events = mPersistentEventsStorage.pop(mConfig.getEventsPerPush()); - if (events.size() > 0) { - long startTime = System.currentTimeMillis(); - long latency = 0; - try { - Logger.d("Posting %d Split events", events.size()); - mHttpRecorder.execute(events); - - long now = System.currentTimeMillis(); - latency = now - startTime; - mTelemetryRuntimeProducer.recordSuccessfulSync(OperationType.EVENTS, now); - - mPersistentEventsStorage.delete(events); - Logger.d("%d split events sent", events.size()); - } catch (HttpRecorderException e) { - status = SplitTaskExecutionStatus.ERROR; - nonSentRecords += mConfig.getEventsPerPush(); - nonSentBytes += sumEventBytes(events); - Logger.e("Event recorder task: Some events couldn't be sent" + - "Saving to send them in a new iteration: " + - e.getLocalizedMessage()); - failingEvents.addAll(events); - - mTelemetryRuntimeProducer.recordSyncError(OperationType.EVENTS, e.getHttpStatus()); - - if (HttpStatus.isNotRetryable(e.getHttpStatus())) { - doNotRetry = true; - break; - } - } finally { - mTelemetryRuntimeProducer.recordSyncLatency(OperationType.EVENTS, latency); - } - } - } while (events.size() == mConfig.getEventsPerPush()); - - // Update events by chunks to avoid sqlite errors - List> failingChunks = partition(failingEvents, FAILING_CHUNK_SIZE); - for (List chunk : failingChunks) { - mPersistentEventsStorage.setActive(chunk); - } - - if (status == SplitTaskExecutionStatus.ERROR) { - Map data = new HashMap<>(); - data.put(SplitTaskExecutionInfo.NON_SENT_RECORDS, nonSentRecords); - data.put(SplitTaskExecutionInfo.NON_SENT_BYTES, nonSentBytes); - if (doNotRetry) { - data.put(SplitTaskExecutionInfo.DO_NOT_RETRY, true); - } - - return SplitTaskExecutionInfo.error( - SplitTaskType.EVENTS_RECORDER, data); - } - return SplitTaskExecutionInfo.success(SplitTaskType.EVENTS_RECORDER); + protected List transformForSubmission(List items) { + return items; } - private long sumEventBytes(List events) { - long totalBytes = 0; - for (Event event : events) { - totalBytes += event.getSizeInBytes(); - } - return totalBytes; + @Override + protected long estimateItemSize(Event item) { + return item.getSizeInBytes(); } } diff --git a/main/src/main/java/io/split/android/client/service/impressions/ImpressionsCountRecorderTask.java b/main/src/main/java/io/split/android/client/service/impressions/ImpressionsCountRecorderTask.java index cd761d75b..32ae11600 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/ImpressionsCountRecorderTask.java +++ b/main/src/main/java/io/split/android/client/service/impressions/ImpressionsCountRecorderTask.java @@ -1,96 +1,34 @@ package io.split.android.client.service.impressions; -import static io.split.android.client.utils.Utils.checkNotNull; - import androidx.annotation.NonNull; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import io.split.android.client.service.HttpRecorderSubmitterAdapter; import io.split.android.client.service.ServiceConstants; -import io.split.android.client.service.executor.SplitTask; -import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.TelemetryRecorderAdapter; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; -import io.split.android.client.service.http.HttpRecorderException; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.storage.impressions.PersistentImpressionsCountStorage; +import io.split.android.client.submitter.RecorderTask; import io.split.android.client.telemetry.model.OperationType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; -import io.split.android.client.utils.logger.Logger; -public class ImpressionsCountRecorderTask implements SplitTask { - private final PersistentImpressionsCountStorage mPersistentStorage; - private final HttpRecorder mHttpRecorder; - private static int POP_COUNT = ServiceConstants.DEFAULT_IMPRESSION_COUNT_ROWS_POP; - private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; +public class ImpressionsCountRecorderTask extends RecorderTask { public ImpressionsCountRecorderTask(@NonNull HttpRecorder httpRecorder, @NonNull PersistentImpressionsCountStorage persistentStorage, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) { - mHttpRecorder = checkNotNull(httpRecorder); - mPersistentStorage = checkNotNull(persistentStorage); - mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); + super(persistentStorage, + new HttpRecorderSubmitterAdapter<>(httpRecorder), + ServiceConstants.DEFAULT_IMPRESSION_COUNT_ROWS_POP, + SplitTaskType.IMPRESSIONS_COUNT_RECORDER, + new TelemetryRecorderAdapter(telemetryRuntimeProducer, OperationType.IMPRESSIONS_COUNT), + 0); } @Override - @NonNull - public SplitTaskExecutionInfo execute() { - SplitTaskExecutionStatus status = SplitTaskExecutionStatus.SUCCESS; - - List countList = new ArrayList<>(); - List failedSent = new ArrayList<>(); - boolean doNotRetry = false; - do { - countList = mPersistentStorage.pop(POP_COUNT); - if (countList.size() > 0) { - long startTime = System.currentTimeMillis(); - long latency = 0; - try { - Logger.d("Posting %d Split impressions count", countList.size()); - mHttpRecorder.execute(new ImpressionsCount(countList)); - - long now = System.currentTimeMillis(); - latency = now - startTime; - mTelemetryRuntimeProducer.recordSuccessfulSync(OperationType.IMPRESSIONS_COUNT, now); - - mPersistentStorage.delete(countList); - Logger.d("%d split impressions count sent", countList.size()); - } catch (HttpRecorderException e) { - status = SplitTaskExecutionStatus.ERROR; - Logger.e("Impressions count recorder task: Some counts couldn't be sent. " + - "Saving to send them in a new iteration\n" + - e.getLocalizedMessage()); - failedSent.addAll(countList); - - mTelemetryRuntimeProducer.recordSyncError(OperationType.IMPRESSIONS_COUNT, e.getHttpStatus()); - - if (HttpStatus.isNotRetryable(HttpStatus.fromCode(e.getHttpStatus()))) { - doNotRetry = true; - break; - } - } finally { - mTelemetryRuntimeProducer.recordSyncLatency(OperationType.IMPRESSIONS_COUNT, latency); - } - } - } while (countList.size() == POP_COUNT); - - if (failedSent.size() > 0) { - mPersistentStorage.setActive(failedSent); - } - - if (status == SplitTaskExecutionStatus.ERROR) { - Map data = new HashMap<>(); - if (doNotRetry) { - data.put(SplitTaskExecutionInfo.DO_NOT_RETRY, true); - } - - return SplitTaskExecutionInfo.error(SplitTaskType.IMPRESSIONS_COUNT_RECORDER, data); - } - - return SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_COUNT_RECORDER); + protected ImpressionsCount transformForSubmission(List items) { + return new ImpressionsCount(items); } } diff --git a/main/src/main/java/io/split/android/client/service/impressions/ImpressionsRecorderTask.java b/main/src/main/java/io/split/android/client/service/impressions/ImpressionsRecorderTask.java index 7a4b122c9..d6fdabb7b 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/ImpressionsRecorderTask.java +++ b/main/src/main/java/io/split/android/client/service/impressions/ImpressionsRecorderTask.java @@ -1,112 +1,43 @@ package io.split.android.client.service.impressions; -import static io.split.android.client.utils.Utils.checkNotNull; - import androidx.annotation.NonNull; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import io.split.android.client.dtos.KeyImpression; -import io.split.android.client.service.executor.SplitTask; -import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.HttpRecorderSubmitterAdapter; +import io.split.android.client.service.TelemetryRecorderAdapter; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; -import io.split.android.client.service.http.HttpRecorderException; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.storage.impressions.PersistentImpressionsStorage; +import io.split.android.client.submitter.RecorderTask; import io.split.android.client.telemetry.model.OperationType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; -import io.split.android.client.utils.logger.Logger; -public class ImpressionsRecorderTask implements SplitTask { - public final static int FAILING_CHUNK_SIZE = 20; - private final PersistentImpressionsStorage mPersistenImpressionsStorage; - private final HttpRecorder> mHttpRecorder; - private final ImpressionsRecorderTaskConfig mConfig; - private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; +public class ImpressionsRecorderTask extends RecorderTask> { + + private final long mEstimatedSizeInBytes; public ImpressionsRecorderTask(@NonNull HttpRecorder> httpRecorder, - @NonNull PersistentImpressionsStorage persistenEventsStorage, + @NonNull PersistentImpressionsStorage storage, @NonNull ImpressionsRecorderTaskConfig config, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer) { - mHttpRecorder = checkNotNull(httpRecorder); - mPersistenImpressionsStorage = checkNotNull(persistenEventsStorage); - mConfig = checkNotNull(config); - mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); + super(storage, + new HttpRecorderSubmitterAdapter<>(httpRecorder), + config.getImpressionsPerPush(), + SplitTaskType.IMPRESSIONS_RECORDER, + new TelemetryRecorderAdapter(telemetryRuntimeProducer, OperationType.IMPRESSIONS), + 0); + this.mEstimatedSizeInBytes = config.getEstimatedSizeInBytes(); } @Override - @NonNull - public SplitTaskExecutionInfo execute() { - SplitTaskExecutionStatus status = SplitTaskExecutionStatus.SUCCESS; - int nonSentRecords = 0; - long nonSentBytes = 0; - List impressions; - List failingImpressions = new ArrayList<>(); - boolean doNotRetry = false; - do { - impressions = mPersistenImpressionsStorage.pop(mConfig.getImpressionsPerPush()); - if (impressions.size() > 0) { - long startTime = System.currentTimeMillis(); - long latency = 0; - try { - Logger.d("Posting %d Split impressions", impressions.size()); - mHttpRecorder.execute(impressions); - - long now = System.currentTimeMillis(); - latency = now - startTime; - mTelemetryRuntimeProducer.recordSuccessfulSync(OperationType.IMPRESSIONS, now); - - mPersistenImpressionsStorage.delete(impressions); - Logger.d("%d split impressions sent", impressions.size()); - } catch (HttpRecorderException e) { - status = SplitTaskExecutionStatus.ERROR; - nonSentRecords += mConfig.getImpressionsPerPush(); - nonSentBytes += sumImpressionsBytes(impressions); - Logger.e("Impressions recorder task: Some impressions couldn't be sent. " + - "Saving to send them in a new iteration\n" + - e.getLocalizedMessage()); - failingImpressions.addAll(impressions); - - mTelemetryRuntimeProducer.recordSyncError(OperationType.IMPRESSIONS, e.getHttpStatus()); - - if (HttpStatus.isNotRetryable(HttpStatus.fromCode(e.getHttpStatus()))) { - doNotRetry = true; - break; - } - } finally { - mTelemetryRuntimeProducer.recordSyncLatency(OperationType.IMPRESSIONS, latency); - } - } - } while (impressions.size() == mConfig.getImpressionsPerPush()); - - if (failingImpressions.size() > 0) { - mPersistenImpressionsStorage.setActive(failingImpressions); - } - - if (status == SplitTaskExecutionStatus.ERROR) { - Map data = new HashMap<>(); - data.put(SplitTaskExecutionInfo.NON_SENT_RECORDS, nonSentRecords); - data.put(SplitTaskExecutionInfo.NON_SENT_BYTES, nonSentBytes); - if (doNotRetry) { - data.put(SplitTaskExecutionInfo.DO_NOT_RETRY, true); - } - - return SplitTaskExecutionInfo.error( - SplitTaskType.IMPRESSIONS_RECORDER, data); - } - return SplitTaskExecutionInfo.success(SplitTaskType.IMPRESSIONS_RECORDER); + protected List transformForSubmission(List items) { + return items; } - private long sumImpressionsBytes(List impressions) { - long totalBytes = 0; - for (KeyImpression impression : impressions) { - totalBytes += mConfig.getEstimatedSizeInBytes(); - } - return totalBytes; + @Override + protected long estimateItemSize(KeyImpression item) { + return mEstimatedSizeInBytes; } } diff --git a/main/src/main/java/io/split/android/client/service/impressions/SaveImpressionsCountTask.java b/main/src/main/java/io/split/android/client/service/impressions/SaveImpressionsCountTask.java index 564a137c1..8e8e5c303 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/SaveImpressionsCountTask.java +++ b/main/src/main/java/io/split/android/client/service/impressions/SaveImpressionsCountTask.java @@ -6,7 +6,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.impressions.PersistentImpressionsCountStorage; import static io.split.android.client.utils.Utils.checkNotNull; diff --git a/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java b/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java index 79ea6dc1c..8fe7e3a99 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java +++ b/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugStrategy.java @@ -16,7 +16,7 @@ import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; -import io.split.android.client.service.synchronizer.RecorderSyncHelper; +import io.split.android.client.submitter.RecorderSyncHelper; import io.split.android.client.telemetry.model.ImpressionsDataType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; diff --git a/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java b/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java index 36d86ff7b..ad1d8219f 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java +++ b/main/src/main/java/io/split/android/client/service/impressions/strategy/DebugTracker.java @@ -15,7 +15,7 @@ import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; -import io.split.android.client.service.synchronizer.RecorderSyncHelper; +import io.split.android.client.submitter.RecorderSyncHelper; class DebugTracker implements PeriodicTracker { diff --git a/main/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java b/main/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java index c331ee61f..3c1a29235 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java +++ b/main/src/main/java/io/split/android/client/service/impressions/strategy/ImpressionStrategyProvider.java @@ -5,7 +5,7 @@ import io.split.android.client.dtos.KeyImpression; import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutor; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.impressions.ImpressionManagerRetryTimerProviderImpl; import io.split.android.client.service.impressions.ImpressionsCounter; import io.split.android.client.service.impressions.ImpressionsMode; @@ -13,7 +13,7 @@ import io.split.android.client.service.impressions.observer.ImpressionsObserverImpl; import io.split.android.client.service.impressions.unique.UniqueKeysTracker; import io.split.android.client.service.impressions.unique.UniqueKeysTrackerImpl; -import io.split.android.client.service.synchronizer.RecorderSyncHelperImpl; +import io.split.android.client.submitter.RecorderSyncHelperImpl; import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; diff --git a/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java b/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java index 23f7c4b7c..5c3b85323 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java +++ b/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedStrategy.java @@ -18,7 +18,7 @@ import io.split.android.client.service.impressions.ImpressionsCounter; import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; -import io.split.android.client.service.synchronizer.RecorderSyncHelper; +import io.split.android.client.submitter.RecorderSyncHelper; import io.split.android.client.telemetry.model.ImpressionsDataType; import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer; diff --git a/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java b/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java index 5f441dfaf..3ca2bc608 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java +++ b/main/src/main/java/io/split/android/client/service/impressions/strategy/OptimizedTracker.java @@ -15,7 +15,7 @@ import io.split.android.client.service.impressions.ImpressionsTaskFactory; import io.split.android.client.service.impressions.observer.ImpressionsObserver; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; -import io.split.android.client.service.synchronizer.RecorderSyncHelper; +import io.split.android.client.submitter.RecorderSyncHelper; class OptimizedTracker implements PeriodicTracker { diff --git a/main/src/main/java/io/split/android/client/service/impressions/unique/SaveUniqueImpressionsTask.java b/main/src/main/java/io/split/android/client/service/impressions/unique/SaveUniqueImpressionsTask.java index 2772ea031..a25148f7b 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/unique/SaveUniqueImpressionsTask.java +++ b/main/src/main/java/io/split/android/client/service/impressions/unique/SaveUniqueImpressionsTask.java @@ -13,7 +13,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.impressions.PersistentImpressionsUniqueStorage; public class SaveUniqueImpressionsTask implements SplitTask { diff --git a/main/src/main/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTask.java b/main/src/main/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTask.java index a258f16ce..cac00741a 100644 --- a/main/src/main/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTask.java +++ b/main/src/main/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTask.java @@ -1,7 +1,5 @@ package io.split.android.client.service.impressions.unique; -import static io.split.android.client.utils.Utils.checkNotNull; - import androidx.annotation.NonNull; import java.util.ArrayList; @@ -11,111 +9,49 @@ import java.util.Map; import java.util.Set; -import io.split.android.client.service.executor.SplitTask; -import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.HttpRecorderSubmitterAdapter; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; -import io.split.android.client.service.http.HttpRecorderException; -import io.split.android.client.service.http.HttpStatus; import io.split.android.client.storage.impressions.PersistentImpressionsUniqueStorage; -import io.split.android.client.utils.logger.Logger; +import io.split.android.client.submitter.RecorderTask; -public class UniqueKeysRecorderTask implements SplitTask { +public class UniqueKeysRecorderTask extends RecorderTask { - private final HttpRecorder mHttpRecorder; - private final PersistentImpressionsUniqueStorage mStorage; - private final UniqueKeysRecorderTaskConfig mConfig; + private final long mEstimatedSizeInBytes; public UniqueKeysRecorderTask(@NonNull HttpRecorder uniqueImpressionsRecorder, @NonNull PersistentImpressionsUniqueStorage storage, @NonNull UniqueKeysRecorderTaskConfig config) { - mHttpRecorder = checkNotNull(uniqueImpressionsRecorder); - mStorage = checkNotNull(storage); - mConfig = checkNotNull(config); + super(storage, + new HttpRecorderSubmitterAdapter<>(uniqueImpressionsRecorder), + config.getElementsPerPush(), + SplitTaskType.UNIQUE_KEYS_RECORDER_TASK, + null, + 0); + this.mEstimatedSizeInBytes = config.getEstimatedSizeInBytes(); } - @NonNull @Override - public SplitTaskExecutionInfo execute() { - SplitTaskExecutionStatus status = SplitTaskExecutionStatus.SUCCESS; - int nonSentRecords = 0; - long nonSentBytes = 0; - List keys; - List failingKeys = new ArrayList<>(); - boolean doNotRetry = false; - do { - keys = mStorage.pop(mConfig.getElementsPerPush()); - if (keys.size() > 0) { - try { - Logger.d("Posting %d Split MTKs", keys.size()); - mHttpRecorder.execute(buildMTK(keys)); - - mStorage.delete(keys); - Logger.d("%d split MTKs sent", keys.size()); - } catch (HttpRecorderException e) { - status = SplitTaskExecutionStatus.ERROR; - nonSentRecords += mConfig.getElementsPerPush(); - nonSentBytes += sumImpressionsBytes(keys); - Logger.e("MTKs recorder task: Some keys couldn't be sent. " + - "Saving to send them in a new iteration\n" + - e.getLocalizedMessage()); - failingKeys.addAll(keys); - - if (HttpStatus.isNotRetryable(HttpStatus.fromCode(e.getHttpStatus()))) { - doNotRetry = true; - break; - } - } - } - } while (keys.size() == mConfig.getElementsPerPush()); - - if (failingKeys.size() > 0) { - mStorage.setActive(failingKeys); - } - - if (status == SplitTaskExecutionStatus.ERROR) { - Map data = new HashMap<>(); - data.put(SplitTaskExecutionInfo.NON_SENT_RECORDS, nonSentRecords); - data.put(SplitTaskExecutionInfo.NON_SENT_BYTES, nonSentBytes); - if (doNotRetry) { - data.put(SplitTaskExecutionInfo.DO_NOT_RETRY, true); - } - - return SplitTaskExecutionInfo.error( - SplitTaskType.UNIQUE_KEYS_RECORDER_TASK, data); - } - - return SplitTaskExecutionInfo.success(SplitTaskType.UNIQUE_KEYS_RECORDER_TASK); - } - - @NonNull - private static MTK buildMTK(List keys) { + protected MTK transformForSubmission(List items) { Map map = new HashMap<>(); - for (UniqueKey key : keys) { + for (UniqueKey key : items) { String userKey = key.getKey(); if (!map.containsKey(userKey)) { map.put(userKey, new UniqueKey(userKey, new HashSet<>())); } - UniqueKey uniqueKey = map.get(userKey); if (uniqueKey != null) { Set originalFeatures = uniqueKey.getFeatures(); Set newFeatures = key.getFeatures(); newFeatures.addAll(originalFeatures); - map.put(userKey, new UniqueKey(userKey, newFeatures)); } } - return new MTK(new ArrayList<>(map.values())); } - private long sumImpressionsBytes(List keys) { - long totalBytes = 0; - for (UniqueKey key : keys) { - totalBytes += mConfig.getEstimatedSizeInBytes(); - } - return totalBytes; + @Override + protected long estimateItemSize(UniqueKey item) { + return mEstimatedSizeInBytes; } } diff --git a/main/src/main/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfig.java b/main/src/main/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfig.java index 451d58dad..99ab45950 100644 --- a/main/src/main/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfig.java +++ b/main/src/main/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfig.java @@ -1,6 +1,6 @@ package io.split.android.client.service.mysegments; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; public class LoadMySegmentsTaskConfig { diff --git a/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfig.java b/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfig.java index 77ddd812d..42cadcde1 100644 --- a/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfig.java +++ b/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfig.java @@ -3,7 +3,7 @@ import androidx.annotation.NonNull; import io.split.android.client.events.SplitInternalEvent; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.telemetry.model.OperationType; public class MySegmentsSyncTaskConfig { diff --git a/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfig.java b/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfig.java index 5a3caf515..ad5ba08eb 100644 --- a/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfig.java +++ b/main/src/main/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfig.java @@ -3,7 +3,7 @@ import androidx.annotation.NonNull; import io.split.android.client.events.SplitInternalEvent; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.telemetry.model.streaming.UpdatesFromSSEEnum; public class MySegmentsUpdateTaskConfig { diff --git a/main/src/main/java/io/split/android/client/service/rules/LoadRuleBasedSegmentsTask.java b/main/src/main/java/io/split/android/client/service/rules/LoadRuleBasedSegmentsTask.java index ba8731c4a..3a740d6f5 100644 --- a/main/src/main/java/io/split/android/client/service/rules/LoadRuleBasedSegmentsTask.java +++ b/main/src/main/java/io/split/android/client/service/rules/LoadRuleBasedSegmentsTask.java @@ -6,7 +6,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; import io.split.android.client.utils.logger.Logger; diff --git a/main/src/main/java/io/split/android/client/service/rules/RuleBasedSegmentInPlaceUpdateTask.java b/main/src/main/java/io/split/android/client/service/rules/RuleBasedSegmentInPlaceUpdateTask.java index 72c05e4a2..656b5aff0 100644 --- a/main/src/main/java/io/split/android/client/service/rules/RuleBasedSegmentInPlaceUpdateTask.java +++ b/main/src/main/java/io/split/android/client/service/rules/RuleBasedSegmentInPlaceUpdateTask.java @@ -10,7 +10,7 @@ import io.split.android.client.events.metadata.EventMetadataHelpers; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; import io.split.android.client.utils.logger.Logger; diff --git a/main/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java b/main/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java index 2fb16db3a..6b6357f3c 100644 --- a/main/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java +++ b/main/src/main/java/io/split/android/client/service/splits/FilterSplitsInCacheTask.java @@ -12,7 +12,7 @@ import io.split.android.client.dtos.Split; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.splits.PersistentSplitsStorage; import io.split.android.client.utils.logger.Logger; diff --git a/main/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java b/main/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java index 49ca1d512..ab9731bb3 100644 --- a/main/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java +++ b/main/src/main/java/io/split/android/client/service/splits/LoadSplitsTask.java @@ -7,7 +7,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.utils.logger.Logger; diff --git a/main/src/main/java/io/split/android/client/service/splits/RuleBasedSegmentInPlaceUpdateTask.java b/main/src/main/java/io/split/android/client/service/splits/RuleBasedSegmentInPlaceUpdateTask.java index 6fb8fc8dc..2b6d886b5 100644 --- a/main/src/main/java/io/split/android/client/service/splits/RuleBasedSegmentInPlaceUpdateTask.java +++ b/main/src/main/java/io/split/android/client/service/splits/RuleBasedSegmentInPlaceUpdateTask.java @@ -10,7 +10,7 @@ import io.split.android.client.events.metadata.EventMetadataHelpers; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.rules.ProcessedRuleBasedSegmentChange; import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java b/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java index 2c86042b0..2b8345f7d 100644 --- a/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java +++ b/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java @@ -14,7 +14,7 @@ import io.split.android.client.events.metadata.EventMetadataHelpers; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.splits.ProcessedSplitChange; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.streaming.UpdatesFromSSEEnum; diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java b/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java index 001d4ec04..53ee6f518 100644 --- a/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java +++ b/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java @@ -13,7 +13,7 @@ import io.split.android.client.events.metadata.EventMetadataHelpers; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.utils.logger.Logger; diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/main/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index 60155c321..208cc557e 100644 --- a/main/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/main/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -25,7 +25,7 @@ import io.split.android.client.network.SplitHttpHeadersBuilder; import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpFetcher; import io.split.android.client.service.http.HttpFetcherException; import io.split.android.client.service.http.HttpStatus; diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java b/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java index d14600725..8bddb0d04 100644 --- a/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java +++ b/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java @@ -17,7 +17,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.synchronizer.SplitsChangeChecker; import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; import io.split.android.client.storage.splits.SplitsStorage; diff --git a/main/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java b/main/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java index abf55e7fe..d0609534a 100644 --- a/main/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java +++ b/main/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java @@ -19,7 +19,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.sseclient.feedbackchannel.PushManagerEventBroadcaster; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; @@ -30,7 +30,9 @@ import io.split.android.client.service.synchronizer.mysegments.MySegmentsSynchronizerRegistry; import io.split.android.client.service.synchronizer.mysegments.MySegmentsSynchronizerRegistryImpl; import io.split.android.client.shared.UserConsent; -import io.split.android.client.storage.common.StoragePusher; +import io.split.android.client.submitter.RecorderSyncHelper; +import io.split.android.client.submitter.RecorderSyncHelperImpl; +import io.split.android.client.submitter.StoragePusher; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.EventsDataRecordsEnum; import io.split.android.client.telemetry.model.streaming.SyncModeUpdateStreamingEvent; @@ -305,14 +307,12 @@ private void scheduleEventsRecorderTask() { @Override public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { - switch (taskInfo.getTaskType()) { - case SPLITS_SYNC: - mFeatureFlagsSynchronizer.submitLoadingTask(null); - break; - case MY_SEGMENTS_SYNC: - Logger.d("Loading my segments updated in background"); - mMySegmentsSynchronizerRegistry.submitMySegmentsLoadingTask(); - break; + io.split.android.client.service.executor.SplitTaskType type = taskInfo.getTaskType(); + if (type == SplitTaskType.SPLITS_SYNC) { + mFeatureFlagsSynchronizer.submitLoadingTask(null); + } else if (type == SplitTaskType.MY_SEGMENTS_SYNC) { + Logger.d("Loading my segments updated in background"); + mMySegmentsSynchronizerRegistry.submitMySegmentsLoadingTask(); } } } diff --git a/main/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java b/main/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java index 2b86786fd..0e7c94814 100644 --- a/main/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java +++ b/main/src/main/java/io/split/android/client/service/synchronizer/WorkManagerWrapper.java @@ -29,7 +29,7 @@ import io.split.android.client.service.ServiceConstants; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.impressions.ImpressionManagerConfig; import io.split.android.client.service.synchronizer.mysegments.MySegmentsWorkManagerWrapper; import io.split.android.client.service.workmanager.EventsRecorderWorker; diff --git a/main/src/main/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTask.java b/main/src/main/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTask.java index 5083359dd..568668d86 100644 --- a/main/src/main/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTask.java +++ b/main/src/main/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTask.java @@ -8,7 +8,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.service.http.HttpStatus; diff --git a/main/src/main/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTask.java b/main/src/main/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTask.java index c6c8fbd21..464055ac2 100644 --- a/main/src/main/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTask.java +++ b/main/src/main/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTask.java @@ -8,7 +8,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.service.http.HttpStatus; diff --git a/main/src/main/java/io/split/android/client/storage/common/PersistentStorage.java b/main/src/main/java/io/split/android/client/storage/common/PersistentStorage.java index c111303d1..21744d98f 100644 --- a/main/src/main/java/io/split/android/client/storage/common/PersistentStorage.java +++ b/main/src/main/java/io/split/android/client/storage/common/PersistentStorage.java @@ -4,16 +4,15 @@ import java.util.List; -public interface PersistentStorage extends StoragePusher { +import io.split.android.client.submitter.RecorderStorage; +import io.split.android.client.submitter.StoragePusher; + +public interface PersistentStorage extends StoragePusher, RecorderStorage { // Push method is defined in StoragePusher interface void pushMany(@NonNull List elements); - List pop(int count); - - void setActive(@NonNull List elements); - - void delete(@NonNull List elements); + // pop, delete, and setActive are inherited from RecorderStorage void deleteInvalid(long maxTimestamp); } diff --git a/main/src/main/java/io/split/android/client/storage/events/EventsStorage.java b/main/src/main/java/io/split/android/client/storage/events/EventsStorage.java index 1eb2d1ae7..5da50977a 100644 --- a/main/src/main/java/io/split/android/client/storage/events/EventsStorage.java +++ b/main/src/main/java/io/split/android/client/storage/events/EventsStorage.java @@ -11,7 +11,7 @@ import io.split.android.client.dtos.Event; import io.split.android.client.storage.common.Storage; -import io.split.android.client.storage.common.StoragePusher; +import io.split.android.client.submitter.StoragePusher; import io.split.android.client.utils.logger.Logger; public class EventsStorage implements Storage, StoragePusher { diff --git a/main/src/main/java/io/split/android/client/storage/impressions/ImpressionsStorage.java b/main/src/main/java/io/split/android/client/storage/impressions/ImpressionsStorage.java index 403fbe220..188e3d2db 100644 --- a/main/src/main/java/io/split/android/client/storage/impressions/ImpressionsStorage.java +++ b/main/src/main/java/io/split/android/client/storage/impressions/ImpressionsStorage.java @@ -12,7 +12,7 @@ import io.split.android.client.dtos.KeyImpression; import io.split.android.client.storage.common.PersistentStorage; import io.split.android.client.storage.common.Storage; -import io.split.android.client.storage.common.StoragePusher; +import io.split.android.client.submitter.StoragePusher; import io.split.android.client.utils.logger.Logger; public class ImpressionsStorage implements Storage, StoragePusher { diff --git a/main/src/test/java/io/split/android/client/service/EventsRecorderTaskTest.java b/main/src/test/java/io/split/android/client/service/EventsRecorderTaskTest.java index 9fe3c763a..f3c9d5420 100644 --- a/main/src/test/java/io/split/android/client/service/EventsRecorderTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/EventsRecorderTaskTest.java @@ -24,7 +24,7 @@ import io.split.android.client.service.events.EventsRecorderTaskConfig; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.storage.events.PersistentEventsStorage; diff --git a/main/src/test/java/io/split/android/client/service/ImpressionsCountRecorderTaskTest.java b/main/src/test/java/io/split/android/client/service/ImpressionsCountRecorderTaskTest.java index 11d20af45..d8ac9d9ba 100644 --- a/main/src/test/java/io/split/android/client/service/ImpressionsCountRecorderTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/ImpressionsCountRecorderTaskTest.java @@ -24,7 +24,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.service.impressions.ImpressionsCount; diff --git a/main/src/test/java/io/split/android/client/service/ImpressionsRecorderTaskTest.java b/main/src/test/java/io/split/android/client/service/ImpressionsRecorderTaskTest.java index 913cd9511..632be84ab 100644 --- a/main/src/test/java/io/split/android/client/service/ImpressionsRecorderTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/ImpressionsRecorderTaskTest.java @@ -25,7 +25,7 @@ import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.service.impressions.ImpressionsRecorderTask; diff --git a/main/src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java b/main/src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java index 0720c7064..f07aa402f 100644 --- a/main/src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/LoadSplitsTaskTest.java @@ -11,7 +11,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.splits.LoadSplitsTask; import io.split.android.client.storage.splits.SplitsStorage; diff --git a/main/src/test/java/io/split/android/client/service/MySegmentsUpdateTaskTest.java b/main/src/test/java/io/split/android/client/service/MySegmentsUpdateTaskTest.java index 663602b30..109d32bbc 100644 --- a/main/src/test/java/io/split/android/client/service/MySegmentsUpdateTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/MySegmentsUpdateTaskTest.java @@ -29,7 +29,7 @@ import io.split.android.client.events.SplitInternalEvent; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpFetcherException; import io.split.android.client.service.mysegments.MySegmentsUpdateTask; import io.split.android.client.service.mysegments.MySegmentsUpdateTaskConfig; diff --git a/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java b/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java index beb9d4043..e3dd8261b 100644 --- a/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java @@ -17,7 +17,7 @@ import io.split.android.client.events.SplitInternalEvent; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpFetcherException; import io.split.android.client.service.splits.SplitKillTask; import io.split.android.client.storage.splits.SplitsStorage; diff --git a/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java b/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java index 45bc7b228..e63578b11 100644 --- a/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java @@ -36,7 +36,7 @@ import io.split.android.client.events.SplitEventsManager; import io.split.android.client.events.SplitInternalEvent; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpFetcherException; import io.split.android.client.service.splits.SplitsSyncHelper; import io.split.android.client.service.splits.SplitsSyncTask; diff --git a/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java b/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java index fda1b1a89..d46d9dc04 100644 --- a/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java @@ -29,7 +29,7 @@ import io.split.android.client.events.SplitInternalEvent; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpFetcherException; import io.split.android.client.service.splits.SplitsSyncHelper; import io.split.android.client.service.splits.SplitsUpdateTask; diff --git a/main/src/test/java/io/split/android/client/service/SynchronizerTest.java b/main/src/test/java/io/split/android/client/service/SynchronizerTest.java index 6a5e4aa15..c6eb2ac1b 100644 --- a/main/src/test/java/io/split/android/client/service/SynchronizerTest.java +++ b/main/src/test/java/io/split/android/client/service/SynchronizerTest.java @@ -53,7 +53,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpFetcher; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.impressions.ImpressionManagerConfig; @@ -71,7 +71,7 @@ import io.split.android.client.service.splits.SplitsUpdateTask; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; import io.split.android.client.service.synchronizer.FeatureFlagsSynchronizer; -import io.split.android.client.service.synchronizer.RecorderSyncHelper; +import io.split.android.client.submitter.RecorderSyncHelper; import io.split.android.client.service.synchronizer.SynchronizerImpl; import io.split.android.client.service.synchronizer.WorkManagerWrapper; import io.split.android.client.service.synchronizer.attributes.AttributesSynchronizer; diff --git a/main/src/test/java/io/split/android/client/service/attributes/LoadAttributesTaskTest.java b/main/src/test/java/io/split/android/client/service/attributes/LoadAttributesTaskTest.java index 052581c27..eb71fc99c 100644 --- a/main/src/test/java/io/split/android/client/service/attributes/LoadAttributesTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/attributes/LoadAttributesTaskTest.java @@ -15,7 +15,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.storage.attributes.AttributesStorage; import io.split.android.client.storage.attributes.PersistentAttributesStorage; diff --git a/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt b/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt index 333a16b8d..cf6034c85 100644 --- a/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt +++ b/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugStrategyTest.kt @@ -4,11 +4,11 @@ import io.split.android.client.dtos.KeyImpression import io.split.android.client.service.executor.SplitTaskExecutionInfo import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor -import io.split.android.client.service.executor.SplitTaskType +import io.split.android.client.service.SplitTaskType import io.split.android.client.service.impressions.ImpressionsRecorderTask import io.split.android.client.service.impressions.ImpressionsTaskFactory import io.split.android.client.service.impressions.observer.ImpressionsObserver -import io.split.android.client.service.synchronizer.RecorderSyncHelper +import io.split.android.client.submitter.RecorderSyncHelper import io.split.android.client.telemetry.model.ImpressionsDataType import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer import org.junit.Before diff --git a/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt b/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt index 6ddca42f7..76d5ee1d9 100644 --- a/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt +++ b/main/src/test/java/io/split/android/client/service/impressions/strategy/DebugTrackerTest.kt @@ -4,12 +4,12 @@ import io.split.android.client.dtos.KeyImpression import io.split.android.client.service.executor.SplitTaskExecutionInfo import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor -import io.split.android.client.service.executor.SplitTaskType +import io.split.android.client.service.SplitTaskType import io.split.android.client.service.impressions.ImpressionsRecorderTask import io.split.android.client.service.impressions.ImpressionsTaskFactory import io.split.android.client.service.impressions.observer.ImpressionsObserver import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer -import io.split.android.client.service.synchronizer.RecorderSyncHelper +import io.split.android.client.submitter.RecorderSyncHelper import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor diff --git a/main/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt b/main/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt index 8d1ebe1f9..617a578f9 100644 --- a/main/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt +++ b/main/src/test/java/io/split/android/client/service/impressions/strategy/NoneTrackerTest.kt @@ -6,7 +6,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo.DO_NOT_RE import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor import io.split.android.client.service.executor.SplitTaskSerialWrapper -import io.split.android.client.service.executor.SplitTaskType +import io.split.android.client.service.SplitTaskType import io.split.android.client.service.impressions.* import io.split.android.client.service.impressions.unique.SaveUniqueImpressionsTask import io.split.android.client.service.impressions.unique.UniqueKeysRecorderTask diff --git a/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt b/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt index 198e4d43b..ac312c089 100644 --- a/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt +++ b/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedStrategyTest.kt @@ -6,12 +6,12 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo import io.split.android.client.service.executor.SplitTaskExecutionInfo.DO_NOT_RETRY import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor -import io.split.android.client.service.executor.SplitTaskType +import io.split.android.client.service.SplitTaskType import io.split.android.client.service.impressions.ImpressionsCounter import io.split.android.client.service.impressions.ImpressionsRecorderTask import io.split.android.client.service.impressions.ImpressionsTaskFactory import io.split.android.client.service.impressions.observer.ImpressionsObserverImpl -import io.split.android.client.service.synchronizer.RecorderSyncHelper +import io.split.android.client.submitter.RecorderSyncHelper import io.split.android.client.telemetry.model.ImpressionsDataType import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer import org.junit.Before diff --git a/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt b/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt index 71915b9ca..dca54065c 100644 --- a/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt +++ b/main/src/test/java/io/split/android/client/service/impressions/strategy/OptimizedTrackerTest.kt @@ -6,11 +6,11 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo.DO_NOT_RE import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.service.executor.SplitTaskExecutor import io.split.android.client.service.executor.SplitTaskSerialWrapper -import io.split.android.client.service.executor.SplitTaskType +import io.split.android.client.service.SplitTaskType import io.split.android.client.service.impressions.* import io.split.android.client.service.impressions.observer.ImpressionsObserver import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer -import io.split.android.client.service.synchronizer.RecorderSyncHelper +import io.split.android.client.submitter.RecorderSyncHelper import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor diff --git a/main/src/test/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTaskTest.java b/main/src/test/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTaskTest.java index 93839a0ad..f12c33e44 100644 --- a/main/src/test/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/impressions/unique/UniqueKeysRecorderTaskTest.java @@ -25,7 +25,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.storage.impressions.PersistentImpressionsUniqueStorage; diff --git a/main/src/test/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfigTest.java b/main/src/test/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfigTest.java index 9973189e6..6e1e224de 100644 --- a/main/src/test/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfigTest.java +++ b/main/src/test/java/io/split/android/client/service/mysegments/LoadMySegmentsTaskConfigTest.java @@ -4,7 +4,7 @@ import org.junit.Test; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; public class LoadMySegmentsTaskConfigTest { diff --git a/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfigTest.java b/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfigTest.java index 7e2d8b3c0..207c5c479 100644 --- a/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfigTest.java +++ b/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsSyncTaskConfigTest.java @@ -5,7 +5,7 @@ import org.junit.Test; import io.split.android.client.events.SplitInternalEvent; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.telemetry.model.OperationType; public class MySegmentsSyncTaskConfigTest { diff --git a/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfigTest.java b/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfigTest.java index ea7f26181..b212b848e 100644 --- a/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfigTest.java +++ b/main/src/test/java/io/split/android/client/service/mysegments/MySegmentsUpdateTaskConfigTest.java @@ -5,7 +5,7 @@ import org.junit.Test; import io.split.android.client.events.SplitInternalEvent; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.telemetry.model.streaming.UpdatesFromSSEEnum; public class MySegmentsUpdateTaskConfigTest { diff --git a/main/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java b/main/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java index b946113db..8fe8012c4 100644 --- a/main/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java +++ b/main/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java @@ -27,7 +27,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.rules.RuleBasedSegmentInPlaceUpdateTask; import io.split.android.client.service.splits.SplitInPlaceUpdateTask; import io.split.android.client.service.sseclient.notifications.InstantUpdateChangeNotification; diff --git a/main/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java b/main/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java index 02a7afabc..2aafc3dcd 100644 --- a/main/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java +++ b/main/src/test/java/io/split/android/client/service/sseclient/sseclient/RetryBackoffCounterTimerTest.java @@ -26,7 +26,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.executor.SplitTaskExecutor; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.backoff.BackoffCounter; public class RetryBackoffCounterTimerTest { diff --git a/main/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java b/main/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java index 515d3dd3e..32215d873 100644 --- a/main/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java +++ b/main/src/test/java/io/split/android/client/service/synchronizer/FeatureFlagsSynchronizerImplTest.java @@ -34,7 +34,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.rules.LoadRuleBasedSegmentsTask; import io.split.android.client.service.splits.FilterSplitsInCacheTask; import io.split.android.client.service.splits.LoadSplitsTask; diff --git a/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java b/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java index 252c29696..8a6a861e9 100644 --- a/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java +++ b/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java @@ -16,7 +16,7 @@ import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.events.SplitInternalEvent; import io.split.android.client.service.executor.SplitTaskExecutionInfo; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; public class LoadLocalDataListenerTest { diff --git a/main/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java b/main/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java index 1888bf831..56d1be867 100644 --- a/main/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java +++ b/main/src/test/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImplTest.java @@ -10,8 +10,9 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; -import io.split.android.client.service.executor.SplitTaskType; -import io.split.android.client.storage.common.StoragePusher; +import io.split.android.client.service.SplitTaskType; +import io.split.android.client.submitter.RecorderSyncHelperImpl; +import io.split.android.client.submitter.StoragePusher; public class RecorderSyncHelperImplTest { diff --git a/main/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImplTest.java b/main/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImplTest.java index 2c87b101a..a542cbd0a 100644 --- a/main/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImplTest.java +++ b/main/src/test/java/io/split/android/client/service/synchronizer/mysegments/MySegmentsSynchronizerImplTest.java @@ -28,7 +28,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.mysegments.LoadMySegmentsTask; import io.split.android.client.service.mysegments.MySegmentUpdateParams; import io.split.android.client.service.mysegments.MySegmentsSyncTask; diff --git a/main/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java b/main/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java index 2c83e3ada..914b73e8f 100644 --- a/main/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java +++ b/main/src/test/java/io/split/android/client/service/telemetry/SynchronizerImplTelemetryTest.java @@ -22,7 +22,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.impressions.StrategyImpressionManager; import io.split.android.client.service.splits.SplitsSyncTask; import io.split.android.client.service.sseclient.feedbackchannel.PushManagerEventBroadcaster; diff --git a/main/src/test/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTaskTest.java b/main/src/test/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTaskTest.java index f3182ffdc..6ee10c633 100644 --- a/main/src/test/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/telemetry/TelemetryConfigRecorderTaskTest.java @@ -20,7 +20,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.telemetry.model.Config; diff --git a/main/src/test/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTaskTest.java b/main/src/test/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTaskTest.java index 7ab3064ae..adbe1137e 100644 --- a/main/src/test/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTaskTest.java +++ b/main/src/test/java/io/split/android/client/service/telemetry/TelemetryStatsRecorderTaskTest.java @@ -19,7 +19,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionStatus; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.http.HttpRecorder; import io.split.android.client.service.http.HttpRecorderException; import io.split.android.client.telemetry.model.OperationType; diff --git a/main/src/test/java/io/split/android/client/telemetry/TelemetrySynchronizerImplTest.java b/main/src/test/java/io/split/android/client/telemetry/TelemetrySynchronizerImplTest.java index bf099811e..2c565c82c 100644 --- a/main/src/test/java/io/split/android/client/telemetry/TelemetrySynchronizerImplTest.java +++ b/main/src/test/java/io/split/android/client/telemetry/TelemetrySynchronizerImplTest.java @@ -22,7 +22,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutor; -import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.SplitTaskType; import io.split.android.client.service.sseclient.sseclient.RetryBackoffCounterTimer; import io.split.android.client.service.telemetry.TelemetryConfigRecorderTask; import io.split.android.client.service.telemetry.TelemetryStatsRecorderTask; diff --git a/settings.gradle b/settings.gradle index 4b3af1a51..659c104d3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,5 +8,7 @@ include ':fallback' include ':main' include ':events' include ':events-domain' +include ':executor' include ':backoff' include ':tracker' +include ':submitter' diff --git a/submitter/.gitignore b/submitter/.gitignore new file mode 100644 index 000000000..e4dbec6f2 --- /dev/null +++ b/submitter/.gitignore @@ -0,0 +1,3 @@ +/build +.classpath +.settings diff --git a/submitter/README.md b/submitter/README.md new file mode 100644 index 000000000..5735f8783 --- /dev/null +++ b/submitter/README.md @@ -0,0 +1,12 @@ +# submitter + +Generic batch recorder task abstraction. + +## Purpose + +Encapsulates the logic for submitting batched data (such as impressions and events) to the backend. It provides a reusable abstraction for recorder tasks, decoupled from the SDK's internal storage and networking layers. Dependencies are injected via callbacks. + +## Design notes + +- For now depends on `events-domain` for the executor types. +- Depends on `logger` for logging. diff --git a/submitter/build.gradle b/submitter/build.gradle new file mode 100644 index 000000000..02bba6959 --- /dev/null +++ b/submitter/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'com.android.library' +} + +apply from: "$projectDir/../gradle/common-android-library.gradle" + +android { + namespace 'io.split.android.client.submitter' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation clientModuleProject('executor') + implementation clientModuleProject('logger') + implementation libs.annotation + + testImplementation libs.junit4 + testImplementation libs.mockitoCore +} diff --git a/submitter/src/main/AndroidManifest.xml b/submitter/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9a40236b9 --- /dev/null +++ b/submitter/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/main/src/main/java/io/split/android/client/storage/common/InBytesSizable.java b/submitter/src/main/java/io/split/android/client/submitter/InBytesSizable.java similarity index 57% rename from main/src/main/java/io/split/android/client/storage/common/InBytesSizable.java rename to submitter/src/main/java/io/split/android/client/submitter/InBytesSizable.java index 645224611..6cb1fbcd1 100644 --- a/main/src/main/java/io/split/android/client/storage/common/InBytesSizable.java +++ b/submitter/src/main/java/io/split/android/client/submitter/InBytesSizable.java @@ -1,4 +1,4 @@ -package io.split.android.client.storage.common; +package io.split.android.client.submitter; public interface InBytesSizable { long getSizeInBytes(); diff --git a/submitter/src/main/java/io/split/android/client/submitter/RecorderException.java b/submitter/src/main/java/io/split/android/client/submitter/RecorderException.java new file mode 100644 index 000000000..31e49ffcb --- /dev/null +++ b/submitter/src/main/java/io/split/android/client/submitter/RecorderException.java @@ -0,0 +1,20 @@ +package io.split.android.client.submitter; + +public class RecorderException extends Exception { + private final Integer mHttpStatus; + private final boolean mRetryable; + + public RecorderException(String message, Integer httpStatus, boolean retryable) { + super(message); + this.mHttpStatus = httpStatus; + this.mRetryable = retryable; + } + + public Integer getHttpStatus() { + return mHttpStatus; + } + + public boolean isRetryable() { + return mRetryable; + } +} diff --git a/submitter/src/main/java/io/split/android/client/submitter/RecorderStorage.java b/submitter/src/main/java/io/split/android/client/submitter/RecorderStorage.java new file mode 100644 index 000000000..350190c54 --- /dev/null +++ b/submitter/src/main/java/io/split/android/client/submitter/RecorderStorage.java @@ -0,0 +1,10 @@ +package io.split.android.client.submitter; + +import androidx.annotation.NonNull; +import java.util.List; + +public interface RecorderStorage { + List pop(int count); + void delete(@NonNull List items); + void setActive(@NonNull List items); +} diff --git a/submitter/src/main/java/io/split/android/client/submitter/RecorderSubmitter.java b/submitter/src/main/java/io/split/android/client/submitter/RecorderSubmitter.java new file mode 100644 index 000000000..9ea278617 --- /dev/null +++ b/submitter/src/main/java/io/split/android/client/submitter/RecorderSubmitter.java @@ -0,0 +1,7 @@ +package io.split.android.client.submitter; + +import androidx.annotation.NonNull; + +public interface RecorderSubmitter { + void execute(@NonNull T data) throws RecorderException; +} diff --git a/main/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelper.java b/submitter/src/main/java/io/split/android/client/submitter/RecorderSyncHelper.java similarity index 75% rename from main/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelper.java rename to submitter/src/main/java/io/split/android/client/submitter/RecorderSyncHelper.java index fc84c75e4..fd70cba57 100644 --- a/main/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelper.java +++ b/submitter/src/main/java/io/split/android/client/submitter/RecorderSyncHelper.java @@ -1,7 +1,6 @@ -package io.split.android.client.service.synchronizer; +package io.split.android.client.submitter; import io.split.android.client.service.executor.SplitTaskExecutionListener; -import io.split.android.client.storage.common.InBytesSizable; public interface RecorderSyncHelper extends SplitTaskExecutionListener { boolean pushAndCheckIfFlushNeeded(T entity); diff --git a/main/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImpl.java b/submitter/src/main/java/io/split/android/client/submitter/RecorderSyncHelperImpl.java similarity index 90% rename from main/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImpl.java rename to submitter/src/main/java/io/split/android/client/submitter/RecorderSyncHelperImpl.java index 4351930fb..09488a69e 100644 --- a/main/src/main/java/io/split/android/client/service/synchronizer/RecorderSyncHelperImpl.java +++ b/submitter/src/main/java/io/split/android/client/submitter/RecorderSyncHelperImpl.java @@ -1,11 +1,10 @@ -package io.split.android.client.service.synchronizer; - -import static io.split.android.client.utils.Utils.checkNotNull; +package io.split.android.client.submitter; import androidx.annotation.NonNull; import java.lang.ref.WeakReference; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -16,8 +15,6 @@ import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskType; -import io.split.android.client.storage.common.InBytesSizable; -import io.split.android.client.storage.common.StoragePusher; public class RecorderSyncHelperImpl implements RecorderSyncHelper { @@ -35,9 +32,9 @@ public RecorderSyncHelperImpl(SplitTaskType taskType, int maxQueueSize, long maxQueueSizeInBytes, SplitTaskExecutor splitTaskExecutor) { - mTaskType = checkNotNull(taskType); - mStorage = checkNotNull(storage); - mSplitTaskExecutor = checkNotNull(splitTaskExecutor); + mTaskType = Objects.requireNonNull(taskType); + mStorage = Objects.requireNonNull(storage); + mSplitTaskExecutor = Objects.requireNonNull(splitTaskExecutor); mPushedCount = new AtomicInteger(0); mTotalPushedSizeInBytes = new AtomicLong(0); mMaxQueueSize = maxQueueSize; diff --git a/submitter/src/main/java/io/split/android/client/submitter/RecorderTask.java b/submitter/src/main/java/io/split/android/client/submitter/RecorderTask.java new file mode 100644 index 000000000..5a822812e --- /dev/null +++ b/submitter/src/main/java/io/split/android/client/submitter/RecorderTask.java @@ -0,0 +1,145 @@ +package io.split.android.client.submitter; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.split.android.client.service.executor.SplitTask; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionStatus; +import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.utils.logger.Logger; + +/** + * Abstract base class for batch submission tasks. + *

+ * Encapsulates the common pop-submit-retry-setActive pattern used by + * impressions, events, and other batch recorder tasks. + * + * @param The storage item type (e.g., KeyImpression, Event) + * @param The submission payload type (e.g., List, ImpressionsCount) + */ +public abstract class RecorderTask implements SplitTask { + + private final RecorderStorage mStorage; + private final RecorderSubmitter mSubmitter; + private final int mBatchSize; + private final SplitTaskType mTaskType; + @Nullable + private final RecorderTelemetry mTelemetry; + private final int mFailingChunkSize; // 0 = no chunking + + protected RecorderTask(@NonNull RecorderStorage storage, + @NonNull RecorderSubmitter submitter, + int batchSize, + @NonNull SplitTaskType taskType, + @Nullable RecorderTelemetry telemetry, + int failingChunkSize) { + mStorage = storage; + mSubmitter = submitter; + mBatchSize = batchSize; + mTaskType = taskType; + mTelemetry = telemetry; + mFailingChunkSize = failingChunkSize; + } + + @NonNull + @Override + public final SplitTaskExecutionInfo execute() { + SplitTaskExecutionStatus status = SplitTaskExecutionStatus.SUCCESS; + int nonSentRecords = 0; + long nonSentBytes = 0; + List items; + List failingItems = new ArrayList<>(); + boolean doNotRetry = false; + + do { + items = mStorage.pop(mBatchSize); + if (!items.isEmpty()) { + long startTime = System.currentTimeMillis(); + try { + R payload = transformForSubmission(items); + mSubmitter.execute(payload); + + long now = System.currentTimeMillis(); + if (mTelemetry != null) { + mTelemetry.recordSuccess(now); + } + + mStorage.delete(items); + } catch (RecorderException e) { + status = SplitTaskExecutionStatus.ERROR; + nonSentRecords += items.size(); + nonSentBytes += sumBytes(items); + Logger.e("RecorderTask: " + items.size() + " items couldn't be submitted. " + + "Saving to retry in a new iteration: " + e.getLocalizedMessage()); + failingItems.addAll(items); + + if (mTelemetry != null) { + mTelemetry.recordError(e.getHttpStatus()); + } + + if (!e.isRetryable()) { + doNotRetry = true; + break; + } + } finally { + if (mTelemetry != null) { + mTelemetry.recordLatency(System.currentTimeMillis() - startTime); + } + } + } + } while (items.size() == mBatchSize); + + // Re-queue failed items for retry + if (!failingItems.isEmpty()) { + if (mFailingChunkSize > 0) { + // Chunk to avoid SQLite errors (used by EventsRecorderTask) + int size = failingItems.size(); + for (int i = 0; i < size; i += mFailingChunkSize) { + mStorage.setActive(failingItems.subList(i, Math.min(i + mFailingChunkSize, size))); + } + } else { + mStorage.setActive(failingItems); + } + } + + if (status == SplitTaskExecutionStatus.ERROR) { + Map data = new HashMap<>(); + data.put(SplitTaskExecutionInfo.NON_SENT_RECORDS, nonSentRecords); + data.put(SplitTaskExecutionInfo.NON_SENT_BYTES, nonSentBytes); + if (doNotRetry) { + data.put(SplitTaskExecutionInfo.DO_NOT_RETRY, true); + } + return SplitTaskExecutionInfo.error(mTaskType, data); + } + + return SplitTaskExecutionInfo.success(mTaskType); + } + + /** + * Transform storage items into the submission payload before submitting. + */ + protected abstract R transformForSubmission(List items); + + /** + * Estimate the byte size of one storage item for tracking non-sent bytes. + *

+ * Default returns 0. Override to enable byte tracking. + */ + protected long estimateItemSize(T item) { + return 0; + } + + private long sumBytes(List items) { + long total = 0; + for (T item : items) { + total += estimateItemSize(item); + } + return total; + } +} diff --git a/submitter/src/main/java/io/split/android/client/submitter/RecorderTelemetry.java b/submitter/src/main/java/io/split/android/client/submitter/RecorderTelemetry.java new file mode 100644 index 000000000..7f8665ab2 --- /dev/null +++ b/submitter/src/main/java/io/split/android/client/submitter/RecorderTelemetry.java @@ -0,0 +1,7 @@ +package io.split.android.client.submitter; + +public interface RecorderTelemetry { + void recordSuccess(long timestamp); + void recordError(Integer httpStatus); + void recordLatency(long latencyMs); +} diff --git a/main/src/main/java/io/split/android/client/storage/common/StoragePusher.java b/submitter/src/main/java/io/split/android/client/submitter/StoragePusher.java similarity index 69% rename from main/src/main/java/io/split/android/client/storage/common/StoragePusher.java rename to submitter/src/main/java/io/split/android/client/submitter/StoragePusher.java index aa8e6cd6d..1ec49d849 100644 --- a/main/src/main/java/io/split/android/client/storage/common/StoragePusher.java +++ b/submitter/src/main/java/io/split/android/client/submitter/StoragePusher.java @@ -1,4 +1,4 @@ -package io.split.android.client.storage.common; +package io.split.android.client.submitter; import androidx.annotation.NonNull; diff --git a/submitter/src/test/java/io/split/android/client/submitter/RecorderTaskTest.java b/submitter/src/test/java/io/split/android/client/submitter/RecorderTaskTest.java new file mode 100644 index 000000000..763a7d22b --- /dev/null +++ b/submitter/src/test/java/io/split/android/client/submitter/RecorderTaskTest.java @@ -0,0 +1,546 @@ +package io.split.android.client.submitter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskExecutionStatus; +import io.split.android.client.service.executor.SplitTaskType; + +@SuppressWarnings("unchecked") +public class RecorderTaskTest { + + private static final int BATCH_SIZE = 10; + private static final SplitTaskType TASK_TYPE = new SplitTaskType() {}; + + private RecorderStorage mStorage; + private RecorderSubmitter> mSubmitter; + private RecorderTelemetry mTelemetry; + + @Before + public void setUp() { + mStorage = Mockito.mock(RecorderStorage.class); + mSubmitter = Mockito.mock(RecorderSubmitter.class); + mTelemetry = Mockito.mock(RecorderTelemetry.class); + } + + // region Successful submission + + @Test + public void successfulSingleBatchSubmission() throws RecorderException { + List batch = createItems(5); // less than BATCH_SIZE → loop terminates after one iteration + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + verify(mStorage, times(1)).pop(BATCH_SIZE); + verify(mSubmitter, times(1)).execute(batch); + verify(mStorage, times(1)).delete(batch); + verify(mStorage, never()).setActive(any()); + assertEquals(TASK_TYPE, result.getTaskType()); + assertEquals(SplitTaskExecutionStatus.SUCCESS, result.getStatus()); + assertNull(result.getIntegerValue(SplitTaskExecutionInfo.NON_SENT_RECORDS)); + assertNull(result.getLongValue(SplitTaskExecutionInfo.NON_SENT_BYTES)); + assertNull(result.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY)); + } + + @Test + public void successfulMultiBatchSubmissionLoopsUntilSmallBatch() throws RecorderException { + List fullBatch = createItems(BATCH_SIZE); + List partialBatch = createItems(3); + + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(fullBatch) + .thenReturn(fullBatch) + .thenReturn(partialBatch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + // Three pops: full, full, partial (terminates) + verify(mStorage, times(3)).pop(BATCH_SIZE); + verify(mSubmitter, times(3)).execute(any()); + verify(mStorage, times(3)).delete(any()); + verify(mStorage, never()).setActive(any()); + assertEquals(SplitTaskExecutionStatus.SUCCESS, result.getStatus()); + } + + @Test + public void emptyFirstPopSkipsSubmissionAndSucceeds() throws RecorderException { + when(mStorage.pop(BATCH_SIZE)).thenReturn(new ArrayList<>()); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + verify(mStorage, times(1)).pop(BATCH_SIZE); + verify(mSubmitter, never()).execute(any()); + verify(mStorage, never()).delete(any()); + verify(mStorage, never()).setActive(any()); + assertEquals(SplitTaskExecutionStatus.SUCCESS, result.getStatus()); + } + + // endregion + + // region Error handling + + @Test + public void retryableErrorCollectsFailuresAndContinuesLoop() throws RecorderException { + List batch = createItems(BATCH_SIZE); + List partialBatch = createItems(3); + + // First pop returns a full batch (fails), second also returns full (succeeds), + // third returns partial (terminates) + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(batch) + .thenReturn(partialBatch); + + // Throw only on the first call to execute; subsequent calls succeed + doThrow(new RecorderException("retryable error", 500, true)) + .doNothing() + .doNothing() + .when(mSubmitter).execute(any()); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + // Three pops total: two full batches + one partial + verify(mStorage, times(3)).pop(BATCH_SIZE); + // First batch failed → not deleted; second and partial → deleted twice + verify(mStorage, times(2)).delete(any()); + // Failing items (one batch worth) are re-queued + verify(mStorage, times(1)).setActive(any()); + + assertEquals(SplitTaskExecutionStatus.ERROR, result.getStatus()); + assertEquals(Integer.valueOf(BATCH_SIZE), result.getIntegerValue(SplitTaskExecutionInfo.NON_SENT_RECORDS)); + } + + @Test + public void retryableErrorPopulatesNonSentRecordsCount() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + assertEquals(Integer.valueOf(BATCH_SIZE), result.getIntegerValue(SplitTaskExecutionInfo.NON_SENT_RECORDS)); + assertEquals(SplitTaskExecutionStatus.ERROR, result.getStatus()); + } + + @Test + public void nonRetryableErrorStopsLoopImmediately() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(batch); // Would be popped if loop continued + doThrow(new RecorderException("non-retryable", 9009, false)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + // Only one pop — loop broke immediately on non-retryable error + verify(mStorage, times(1)).pop(BATCH_SIZE); + verify(mStorage, never()).delete(any()); + verify(mStorage, times(1)).setActive(any()); + + assertEquals(SplitTaskExecutionStatus.ERROR, result.getStatus()); + assertTrue(Boolean.TRUE.equals(result.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY))); + } + + @Test + public void retryableErrorDoesNotSetDoNotRetry() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + assertNull(result.getBoolValue(SplitTaskExecutionInfo.DO_NOT_RETRY)); + } + + // endregion + + // region setActive + + @Test + public void setActiveIsCalledWithFailedItemsOnError() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mStorage, times(1)).setActive(batch); + } + + @Test + public void setActiveIsNotCalledOnSuccess() throws RecorderException { + List batch = createItems(3); + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mStorage, never()).setActive(any()); + } + + @Test + public void chunkedSetActiveWhenFailingChunkSizeIsPositive() throws RecorderException { + int failingChunkSize = 3; + // Create items whose count is a multiple of failingChunkSize for predictable verification + List batch = createItems(9); // 9 items / chunkSize 3 = 3 setActive calls + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, failingChunkSize); + task.execute(); + + // 9 items chunked into 3 → 3 setActive calls + verify(mStorage, times(3)).setActive(any()); + } + + @Test + public void chunkedSetActiveHandlesNonEvenDivision() throws RecorderException { + int failingChunkSize = 3; + // 10 items / chunkSize 3 = 4 calls (chunks of 3, 3, 3, 1) + List batch = createItems(10); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, failingChunkSize); + task.execute(); + + verify(mStorage, times(4)).setActive(any()); + } + + @Test + public void noChunkingWhenFailingChunkSizeIsZero() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + // No chunking → exactly one setActive call with all failing items + verify(mStorage, times(1)).setActive(batch); + } + + // endregion + + // region Byte tracking via estimateItemSize + + @Test + public void byteTrackingViaEstimateItemSizeOverride() throws RecorderException { + long itemSizeBytes = 50L; + List batch = createItems(BATCH_SIZE); // 10 items * 50 bytes = 500 bytes + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTaskWithItemSize(BATCH_SIZE, TASK_TYPE, mTelemetry, 0, itemSizeBytes); + SplitTaskExecutionInfo result = task.execute(); + + long expectedBytes = BATCH_SIZE * itemSizeBytes; + assertEquals(Long.valueOf(expectedBytes), result.getLongValue(SplitTaskExecutionInfo.NON_SENT_BYTES)); + } + + @Test + public void byteTrackingDefaultsToZeroWhenNotOverridden() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + // Default estimateItemSize returns 0 → nonSentBytes = 0 + assertEquals(Long.valueOf(0L), result.getLongValue(SplitTaskExecutionInfo.NON_SENT_BYTES)); + } + + // endregion + + // region transformForSubmission + + @Test + public void transformForSubmissionHookIsApplied() throws RecorderException { + List batch = createItems(3); + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + // Build a task with a custom transform that wraps items in a new list + RecorderTask> task = new SimpleRecorderTask( + mStorage, mSubmitter, BATCH_SIZE, TASK_TYPE, mTelemetry, 0) { + @Override + protected List transformForSubmission(List items) { + List transformed = new ArrayList<>(); + for (String item : items) { + transformed.add(item.toUpperCase()); + } + return transformed; + } + }; + task.execute(); + + // The submitter should receive the transformed list (all uppercase) + List expectedTransformed = new ArrayList<>(); + for (String item : batch) { + expectedTransformed.add(item.toUpperCase()); + } + verify(mSubmitter, times(1)).execute(expectedTransformed); + } + + // endregion + + // region Null telemetry + + @Test + public void nullTelemetryDoesNotThrowNpeOnSuccess() throws RecorderException { + List batch = createItems(3); + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, null, 0); + // Should not throw + SplitTaskExecutionInfo result = task.execute(); + + assertEquals(SplitTaskExecutionStatus.SUCCESS, result.getStatus()); + } + + @Test + public void nullTelemetryDoesNotThrowNpeOnError() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, null, 0); + // Should not throw + SplitTaskExecutionInfo result = task.execute(); + + assertEquals(SplitTaskExecutionStatus.ERROR, result.getStatus()); + } + + // endregion + + // region Telemetry interactions + + @Test + public void telemetryRecordSuccessCalledOnSuccessfulSubmission() throws RecorderException { + List batch = createItems(3); + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mTelemetry, times(1)).recordSuccess(anyLong()); + } + + @Test + public void telemetryRecordSuccessCalledOncePerBatch() throws RecorderException { + List fullBatch = createItems(BATCH_SIZE); + List partialBatch = createItems(3); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(fullBatch) + .thenReturn(partialBatch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mTelemetry, times(2)).recordSuccess(anyLong()); + } + + @Test + public void telemetryRecordLatencyCalledOnSuccess() throws RecorderException { + List batch = createItems(3); + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mTelemetry, atLeastOnce()).recordLatency(anyLong()); + } + + @Test + public void telemetryRecordLatencyCalledOnError() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mTelemetry, atLeastOnce()).recordLatency(anyLong()); + } + + @Test + public void telemetryRecordErrorCalledWithHttpStatusOnError() throws RecorderException { + int httpStatus = 500; + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", httpStatus, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mTelemetry, times(1)).recordError(httpStatus); + } + + @Test + public void telemetryRecordSuccessNotCalledOnError() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mTelemetry, never()).recordSuccess(anyLong()); + } + + @Test + public void telemetryRecordErrorNotCalledOnSuccess() throws RecorderException { + List batch = createItems(3); + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + task.execute(); + + verify(mTelemetry, never()).recordError(any(Integer.class)); + } + + // endregion + + // region Task type + + @Test + public void taskTypeIsPreservedInSuccessResult() throws RecorderException { + List batch = createItems(3); + when(mStorage.pop(BATCH_SIZE)).thenReturn(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + assertEquals(TASK_TYPE, result.getTaskType()); + } + + @Test + public void taskTypeIsPreservedInErrorResult() throws RecorderException { + List batch = createItems(BATCH_SIZE); + when(mStorage.pop(BATCH_SIZE)) + .thenReturn(batch) + .thenReturn(new ArrayList<>()); + doThrow(new RecorderException("error", 500, true)).when(mSubmitter).execute(batch); + + RecorderTask> task = buildTask(BATCH_SIZE, TASK_TYPE, mTelemetry, 0); + SplitTaskExecutionInfo result = task.execute(); + + assertEquals(TASK_TYPE, result.getTaskType()); + } + + // endregion + + // region Helpers + + private List createItems(int count) { + List items = new ArrayList<>(); + for (int i = 0; i < count; i++) { + items.add("item_" + i); + } + return items; + } + + /** + * Builds a standard {@link RecorderTask} with no custom overrides. + * Uses the default {@link RecorderTask#transformForSubmission} (identity cast) and + * {@link RecorderTask#estimateItemSize} (returns 0). + */ + private RecorderTask> buildTask(int batchSize, + SplitTaskType taskType, + RecorderTelemetry telemetry, + int failingChunkSize) { + return new SimpleRecorderTask(mStorage, mSubmitter, batchSize, taskType, telemetry, failingChunkSize); + } + + /** + * Builds a {@link RecorderTask} with a custom fixed item size returned from + * {@link RecorderTask#estimateItemSize}, to exercise byte tracking. + */ + private RecorderTask> buildTaskWithItemSize(int batchSize, + SplitTaskType taskType, + RecorderTelemetry telemetry, + int failingChunkSize, + long itemSizeBytes) { + return new SimpleRecorderTask(mStorage, mSubmitter, batchSize, taskType, telemetry, failingChunkSize) { + @Override + protected long estimateItemSize(String item) { + return itemSizeBytes; + } + }; + } + + /** + * Minimal concrete subclass of {@link RecorderTask} for testing. + * T = String, R = List<String> (identity transform — same type). + */ + private static class SimpleRecorderTask extends RecorderTask> { + + SimpleRecorderTask(@NonNull RecorderStorage storage, + @NonNull RecorderSubmitter> submitter, + int batchSize, + @NonNull SplitTaskType taskType, + RecorderTelemetry telemetry, + int failingChunkSize) { + super(storage, submitter, batchSize, taskType, telemetry, failingChunkSize); + } + + @Override + protected List transformForSubmission(List items) { + return items; + } + } + + // endregion +} diff --git a/tracker/.gitignore b/tracker/.gitignore index 796b96d1c..e4dbec6f2 100644 --- a/tracker/.gitignore +++ b/tracker/.gitignore @@ -1 +1,3 @@ /build +.classpath +.settings