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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Fixed
- Push token registration and disable operations, including their API requests and retries, now run on a dedicated SDK-owned serial executor, while Iterable deep-link redirects use a separate SDK-owned serial executor. This removes their dependency on Android's process-wide `AsyncTask` queue, preserves ordering within each operation type, prevents slow redirects from delaying push work, and keeps client callbacks and attribution updates on the main thread.
- Push token registration, disable operations, Iterable deep-link redirects, and API requests now use dedicated SDK-owned executors instead of Android's process-wide `AsyncTask` queues. Push and deep-link work retain isolated serial lanes. Ordinary online API requests remain concurrent, while their automatic retries and offline immediate requests retain serial ordering; stored offline requests retain their own FIFO flow. Slow work in one flow no longer delays unrelated SDK operations, and client callbacks and attribution updates continue on the main thread.

## [3.11.0]
### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public void onSuccess(@NonNull JSONObject data) {
signal.countDown();
}
}, null);
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(5, TimeUnit.SECONDS);
assertTrue("onSuccess is called", signal.await(1, TimeUnit.SECONDS));
Expand All @@ -103,7 +103,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(5, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(1, TimeUnit.SECONDS));
Expand All @@ -122,7 +122,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(5, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(1, TimeUnit.SECONDS));
Expand All @@ -141,7 +141,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(5, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(5, TimeUnit.SECONDS));
Expand All @@ -162,7 +162,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(5, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(1, TimeUnit.SECONDS));
Expand All @@ -181,7 +181,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(5, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(1, TimeUnit.SECONDS));
Expand All @@ -200,7 +200,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(5, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(1, TimeUnit.SECONDS));
Expand All @@ -222,7 +222,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
"}");
stubAnyRequestReturningStatusCode(200, responseData);

new IterableRequestTask().execute(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.SuccessHandler() {
dispatchRequest(new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, new IterableHelper.SuccessHandler() {
@Override
public void onSuccess(@NonNull JSONObject successData) {
try {
Expand All @@ -246,7 +246,7 @@ public void onSuccess(@NonNull JSONObject successData) {
}
});

new IterableRequestTask().execute(request);
dispatchRequest(request);
server.takeRequest(5, TimeUnit.SECONDS);

// Await for the background tasks to complete
Expand All @@ -260,8 +260,7 @@ public void testMaxRetriesOnMultipleInvalidJwtPayloads() throws Exception {
}

IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, null, null);
IterableRequestTask task = new IterableRequestTask();
task.execute(request);
dispatchRequest(request);

RecordedRequest request1 = server.takeRequest(5, TimeUnit.SECONDS);
RecordedRequest request2 = server.takeRequest(5, TimeUnit.SECONDS);
Expand All @@ -279,8 +278,7 @@ public void testResponseCode500() throws Exception {
}

IterableApiRequest request = new IterableApiRequest("fake_key", "", new JSONObject(), IterableApiRequest.POST, null, null, null);
IterableRequestTask task = new IterableRequestTask();
task.execute(request);
dispatchRequest(request);

RecordedRequest request1 = server.takeRequest(1, TimeUnit.SECONDS);
RecordedRequest request2 = server.takeRequest(5, TimeUnit.SECONDS);
Expand All @@ -300,7 +298,7 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(1, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(5, TimeUnit.SECONDS));
Expand All @@ -319,9 +317,13 @@ public void onFailure(@NonNull String reason, @Nullable JSONObject data) {
signal.countDown();
}
});
new IterableRequestTask().execute(request);
dispatchRequest(request);

server.takeRequest(1, TimeUnit.SECONDS);
assertTrue("onFailure is called", signal.await(1, TimeUnit.SECONDS));
}

private void dispatchRequest(IterableApiRequest request) {
IterableRequestDispatcher.online().execute(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,6 @@ public static boolean isSDKInitialized() {
return initializationRun && backgroundInitComplete && sdkConfigured;
}


/**
* Register a callback to be notified when SDK initialization completes.
* If the SDK is already initialized, the callback is invoked immediately.
Expand Down Expand Up @@ -992,7 +991,10 @@ static void initializeForPush(@Nullable Context context) {
this.embeddedManager = embeddedManager;
this.pushRegistration = Objects.requireNonNull(pushRegistration);
}

void setRequestDispatcher(IterableRequestDispatcher requestDispatcher) {
apiClient = new IterableApiClient(new IterableApiAuthProvider(),
IterableRequestDispatchers.same(Objects.requireNonNull(requestDispatcher)));
}
//endregion

//region SDK public functions
Expand Down Expand Up @@ -1567,7 +1569,6 @@ public void trackPurchase(double total, @NonNull List<CommerceItem> items, @Null
queueOrExecute(() -> trackPurchase(total, items, dataFields, null), "trackPurchase(" + total + ", " + items.size() + " items, dataFields)");
}


/**
* Tracks a purchase.
* @param total total purchase amount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
class IterableApiClient {
private static final String TAG = "IterableApiClient";
private final @NonNull AuthProvider authProvider;
private final IterablePushRegistrationRequestProcessor pushRegistrationRequestProcessor;
private final @NonNull IterableRequestDispatchers requestDispatchers;
private final OnlineRequestProcessor pushRegistrationRequestProcessor;
// A newer push action invalidates retries from earlier registration or disable requests.
private final AtomicLong pushRegistrationRequestGeneration = new AtomicLong();
private RequestProcessor requestProcessor;
private OfflineRequestProcessor offlineRequestProcessor;

interface AuthProvider {
@Nullable
Expand All @@ -45,33 +47,63 @@ interface AuthProvider {
}

IterableApiClient(@NonNull AuthProvider authProvider) {
this(authProvider, IterableRequestDispatchers.sdk());
}

IterableApiClient(
@NonNull AuthProvider authProvider,
@NonNull IterableRequestDispatchers requestDispatchers
) {
this.authProvider = authProvider;
this.requestDispatchers = requestDispatchers;
pushRegistrationRequestProcessor =
new IterablePushRegistrationRequestProcessor();
new OnlineRequestProcessor(requestDispatchers.push());
}

private RequestProcessor getRequestProcessor() {
private synchronized RequestProcessor getRequestProcessor() {
if (requestProcessor == null) {
requestProcessor = new OnlineRequestProcessor();
requestProcessor = new OnlineRequestProcessor(requestDispatchers.online());
}
return requestProcessor;
}

void setOfflineProcessingEnabled(boolean offlineMode) {
if (offlineMode && this.requestProcessor instanceof OfflineRequestProcessor) {
synchronized void setOfflineProcessingEnabled(boolean offlineMode) {
if (!offlineMode
&& offlineRequestProcessor == null
&& !hasPendingOfflineRequests()) {
if (!(requestProcessor instanceof OnlineRequestProcessor)) {
requestProcessor = new OnlineRequestProcessor(requestDispatchers.online());
}
return;
}
if (!offlineMode && this.requestProcessor instanceof OnlineRequestProcessor) {
return;

// Once needed, keep one offline processor alive while new requests use the
// online processor. Its runner must finish requests queued before offline
// processing was disabled or restored from a previous process.
OfflineRequestProcessor persistentOfflineRequestProcessor =
getOfflineRequestProcessor();
if (offlineMode) {
requestProcessor = persistentOfflineRequestProcessor;
} else if (!(requestProcessor instanceof OnlineRequestProcessor)) {
requestProcessor = new OnlineRequestProcessor(requestDispatchers.online());
}
}

if (this.requestProcessor instanceof OfflineRequestProcessor) {
((OfflineRequestProcessor) this.requestProcessor).dispose();
private OfflineRequestProcessor getOfflineRequestProcessor() {
if (offlineRequestProcessor == null) {
offlineRequestProcessor = new OfflineRequestProcessor(
authProvider.getContext(),
requestDispatchers.offlineImmediate(),
requestDispatchers.offlineStored()
);
}
return offlineRequestProcessor;
}

this.requestProcessor = offlineMode
? new OfflineRequestProcessor(authProvider.getContext())
: new OnlineRequestProcessor();
private boolean hasPendingOfflineRequests() {
return IterableTaskStorage
.sharedInstance(authProvider.getContext())
.hasPendingTasks();
}

void getRemoteConfiguration(IterableHelper.IterableActionHandler actionHandler) {
Expand Down Expand Up @@ -819,11 +851,30 @@ void sendGetRequest(@NonNull String resourcePath, @NonNull JSONObject json, @Non
getRequestProcessor().processGetRequest(authProvider.getApiKey(), resourcePath, json, authProvider.getAuthToken(), onSuccess, onFailure);
}

void onLogout() {
getRequestProcessor().onLogout(authProvider.getContext());
synchronized void onLogout() {
Context context = authProvider.getContext();
RequestProcessor activeRequestProcessor = getRequestProcessor();
if (offlineRequestProcessor != null) {
offlineRequestProcessor.onLogout(context);
} else {
IterableTaskStorage.sharedInstance(context).deleteAllTasks();
}
if (activeRequestProcessor != offlineRequestProcessor) {
activeRequestProcessor.onLogout(context);
}
authProvider.resetAuth();
}

synchronized void dispose() {
if (offlineRequestProcessor != null) {
if (requestProcessor == offlineRequestProcessor) {
requestProcessor = null;
}
offlineRequestProcessor.dispose();
offlineRequestProcessor = null;
}
}

void mergeUser(String sourceEmail, String sourceUserId, String destinationEmail, String destinationUserId, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
JSONObject requestJson = new JSONObject();
try {
Expand Down
Loading
Loading