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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ dependencies {
return candidates.find { findProject(it) != null }
}

['main', 'logger', 'events', 'events-domain', 'api', 'http-api', 'http', 'fallback', 'backoff', 'tracker', 'submitter'].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)
Expand Down
1 change: 1 addition & 0 deletions events-domain/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ android {
dependencies {
implementation libs.annotation

api clientModuleProject('executor')
implementation clientModuleProject('api')
implementation clientModuleProject('events')
implementation clientModuleProject('logger')
Expand Down

This file was deleted.

5 changes: 5 additions & 0 deletions executor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/build
.gradle
local.properties
.classpath
.settings
60 changes: 60 additions & 0 deletions executor/README.md
Original file line number Diff line number Diff line change
@@ -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<Result> parallelExecutor =
new SplitParallelTaskExecutorFactoryImpl().build();

List<Callable<Result>> tasks = Arrays.asList(
() -> fetchData1(),
() -> fetchData2()
);

List<Result> 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.
22 changes: 22 additions & 0 deletions executor/build.gradle
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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()) {
Expand All @@ -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(
Expand All @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
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;

class SplitTaskBatchWrapper implements Runnable {
private final List<SplitTaskBatchItem> mTaskQueue;

SplitTaskBatchWrapper(List<SplitTaskBatchItem> taskQueue) {
mTaskQueue = checkNotNull(taskQueue);
mTaskQueue = Objects.requireNonNull(taskQueue);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
};
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -12,7 +11,7 @@ class TaskWrapper implements Runnable {

TaskWrapper(SplitTask task,
SplitTaskExecutionListener executionListener) {
mTask = checkNotNull(task);
mTask = Objects.requireNonNull(task);
mExecutionListener = new WeakReference<>(executionListener);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
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<T> implements Callable<T> {

private final Callable<T> mCallable;

public SplitDeferredTaskItem(@NonNull Callable<T> callable) {
mCallable = checkNotNull(callable);
mCallable = Objects.requireNonNull(callable);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -27,10 +31,10 @@ public void successfulStatusContainsResultsOfEveryTask() {
List<SplitTaskExecutionInfo> results = (List<SplitTaskExecutionInfo>) 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());
}

Expand All @@ -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);

Expand All @@ -51,10 +55,10 @@ public void unsuccessfulResultContainsExecutionInfoUpToFirstUnsuccessfulTask() {
List<SplitTaskExecutionInfo> results = (List<SplitTaskExecutionInfo>) 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());
}

Expand All @@ -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);

Expand All @@ -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();
Expand Down
1 change: 1 addition & 0 deletions main/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ android {

dependencies {
// Public api modules
api clientModuleProject('executor')
api clientModuleProject('logger')
api clientModuleProject('api')
api clientModuleProject('http-api')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading